1 /*
2 * Copyright (c) 2016-2018, 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 subset of IEEE 802.15.4 MAC primitives.
32 */
33
34 #include "sub_mac.hpp"
35
36 #include <stdio.h>
37
38 #include <openthread/platform/time.h>
39
40 #include "common/code_utils.hpp"
41 #include "common/debug.hpp"
42 #include "common/locator_getters.hpp"
43 #include "common/log.hpp"
44 #include "common/num_utils.hpp"
45 #include "common/random.hpp"
46 #include "common/time.hpp"
47 #include "instance/instance.hpp"
48 #include "mac/mac_frame.hpp"
49
50 namespace ot {
51 namespace Mac {
52
53 RegisterLogModule("SubMac");
54
SubMac(Instance & aInstance)55 SubMac::SubMac(Instance &aInstance)
56 : InstanceLocator(aInstance)
57 , mRadioCaps(Get<Radio>().GetCaps())
58 , mTransmitFrame(Get<Radio>().GetTransmitBuffer())
59 , mCallbacks(aInstance)
60 , mTimer(aInstance)
61 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
62 , mCslTimer(aInstance, SubMac::HandleCslTimer)
63 #endif
64 {
65 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
66 mCslParentAccuracy.Init();
67 #endif
68
69 Init();
70 }
71
Init(void)72 void SubMac::Init(void)
73 {
74 mState = kStateDisabled;
75 mCsmaBackoffs = 0;
76 mTransmitRetries = 0;
77 mShortAddress = kShortAddrInvalid;
78 mExtAddress.Clear();
79 mRxOnWhenIdle = true;
80 mEnergyScanMaxRssi = Radio::kInvalidRssi;
81 mEnergyScanEndTime = Time{0};
82 #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
83 mRetxDelayBackOffExponent = kRetxDelayMinBackoffExponent;
84 #endif
85
86 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
87 mRadioFilterEnabled = false;
88 #endif
89
90 mPrevKey.Clear();
91 mCurrKey.Clear();
92 mNextKey.Clear();
93
94 mFrameCounter = 0;
95 mKeyId = 0;
96 mTimer.Stop();
97
98 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
99 mCslPeriod = 0;
100 mCslChannel = 0;
101 mCslPeerShort = 0;
102 mIsCslSampling = false;
103 mCslSampleTime = TimeMicro{0};
104 mCslLastSync = TimeMicro{0};
105 mCslTimer.Stop();
106 #endif
107 }
108
GetCaps(void) const109 otRadioCaps SubMac::GetCaps(void) const
110 {
111 otRadioCaps caps;
112
113 #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
114 caps = mRadioCaps;
115
116 #if OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE
117 caps |= OT_RADIO_CAPS_ACK_TIMEOUT;
118 #endif
119
120 #if OPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE
121 caps |= OT_RADIO_CAPS_CSMA_BACKOFF;
122 #endif
123
124 #if OPENTHREAD_CONFIG_MAC_SOFTWARE_RETRANSMIT_ENABLE
125 caps |= OT_RADIO_CAPS_TRANSMIT_RETRIES;
126 #endif
127
128 #if OPENTHREAD_CONFIG_MAC_SOFTWARE_ENERGY_SCAN_ENABLE
129 caps |= OT_RADIO_CAPS_ENERGY_SCAN;
130 #endif
131
132 #if OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
133 caps |= OT_RADIO_CAPS_TRANSMIT_SEC;
134 #endif
135
136 #if OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
137 caps |= OT_RADIO_CAPS_TRANSMIT_TIMING;
138 #endif
139
140 #if OPENTHREAD_CONFIG_MAC_SOFTWARE_RX_TIMING_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
141 caps |= OT_RADIO_CAPS_RECEIVE_TIMING;
142 #endif
143
144 #if OPENTHREAD_CONFIG_MAC_SOFTWARE_RX_ON_WHEN_IDLE_ENABLE
145 caps |= OT_RADIO_CAPS_RX_ON_WHEN_IDLE;
146 #endif
147
148 #else
149 caps = OT_RADIO_CAPS_ACK_TIMEOUT | OT_RADIO_CAPS_CSMA_BACKOFF | OT_RADIO_CAPS_TRANSMIT_RETRIES |
150 OT_RADIO_CAPS_ENERGY_SCAN | OT_RADIO_CAPS_TRANSMIT_SEC | OT_RADIO_CAPS_TRANSMIT_TIMING |
151 OT_RADIO_CAPS_RECEIVE_TIMING | OT_RADIO_CAPS_RX_ON_WHEN_IDLE;
152 #endif
153
154 return caps;
155 }
156
SetPanId(PanId aPanId)157 void SubMac::SetPanId(PanId aPanId)
158 {
159 Get<Radio>().SetPanId(aPanId);
160 LogDebg("RadioPanId: 0x%04x", aPanId);
161 }
162
SetShortAddress(ShortAddress aShortAddress)163 void SubMac::SetShortAddress(ShortAddress aShortAddress)
164 {
165 mShortAddress = aShortAddress;
166 Get<Radio>().SetShortAddress(mShortAddress);
167 LogDebg("RadioShortAddress: 0x%04x", mShortAddress);
168 }
169
SetExtAddress(const ExtAddress & aExtAddress)170 void SubMac::SetExtAddress(const ExtAddress &aExtAddress)
171 {
172 ExtAddress address;
173
174 mExtAddress = aExtAddress;
175
176 // Reverse the byte order before setting on radio.
177 address.Set(aExtAddress.m8, ExtAddress::kReverseByteOrder);
178 Get<Radio>().SetExtendedAddress(address);
179
180 LogDebg("RadioExtAddress: %s", mExtAddress.ToString().AsCString());
181 }
182
SetRxOnWhenIdle(bool aRxOnWhenIdle)183 void SubMac::SetRxOnWhenIdle(bool aRxOnWhenIdle)
184 {
185 mRxOnWhenIdle = aRxOnWhenIdle;
186
187 if (RadioSupportsRxOnWhenIdle())
188 {
189 #if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
190 Get<Radio>().SetRxOnWhenIdle(mRxOnWhenIdle);
191 #endif
192 }
193
194 LogDebg("RxOnWhenIdle: %d", mRxOnWhenIdle);
195 }
196
Enable(void)197 Error SubMac::Enable(void)
198 {
199 Error error = kErrorNone;
200
201 VerifyOrExit(mState == kStateDisabled);
202
203 SuccessOrExit(error = Get<Radio>().Enable());
204 SuccessOrExit(error = Get<Radio>().Sleep());
205
206 SetState(kStateSleep);
207
208 exit:
209 SuccessOrAssert(error);
210 return error;
211 }
212
Disable(void)213 Error SubMac::Disable(void)
214 {
215 Error error;
216
217 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
218 mCslTimer.Stop();
219 #endif
220
221 mTimer.Stop();
222 SuccessOrExit(error = Get<Radio>().Sleep());
223 SuccessOrExit(error = Get<Radio>().Disable());
224 SetState(kStateDisabled);
225
226 exit:
227 return error;
228 }
229
Sleep(void)230 Error SubMac::Sleep(void)
231 {
232 Error error = kErrorNone;
233
234 VerifyOrExit(ShouldHandleTransitionToSleep());
235
236 error = Get<Radio>().Sleep();
237
238 exit:
239 if (error != kErrorNone)
240 {
241 LogWarn("RadioSleep() failed, error: %s", ErrorToString(error));
242 }
243 else
244 {
245 SetState(kStateSleep);
246 }
247
248 return error;
249 }
250
Receive(uint8_t aChannel)251 Error SubMac::Receive(uint8_t aChannel)
252 {
253 Error error;
254
255 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
256 if (mRadioFilterEnabled)
257 {
258 error = Get<Radio>().Sleep();
259 }
260 else
261 #endif
262 {
263 error = Get<Radio>().Receive(aChannel);
264 }
265
266 if (error != kErrorNone)
267 {
268 LogWarn("RadioReceive() failed, error: %s", ErrorToString(error));
269 ExitNow();
270 }
271
272 SetState(kStateReceive);
273
274 exit:
275 return error;
276 }
277
278 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
CslSample(void)279 void SubMac::CslSample(void)
280 {
281 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
282 VerifyOrExit(!mRadioFilterEnabled, IgnoreError(Get<Radio>().Sleep()));
283 #endif
284
285 SetState(kStateCslSample);
286
287 if (mIsCslSampling && !RadioSupportsReceiveTiming())
288 {
289 IgnoreError(Get<Radio>().Receive(mCslChannel));
290 ExitNow();
291 }
292
293 #if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
294 IgnoreError(Get<Radio>().Sleep()); // Don't actually sleep for debugging
295 #endif
296
297 exit:
298 return;
299 }
300 #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
301
302 #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
LogReceived(RxFrame * aFrame)303 void SubMac::LogReceived(RxFrame *aFrame)
304 {
305 static constexpr uint8_t kLogStringSize = 72;
306
307 String<kLogStringSize> logString;
308 Address dst;
309 int32_t deviation;
310 uint32_t sampleTime, ahead, after;
311
312 IgnoreError(aFrame->GetDstAddr(dst));
313
314 VerifyOrExit((dst.GetType() == Address::kTypeShort && dst.GetShort() == GetShortAddress()) ||
315 (dst.GetType() == Address::kTypeExtended && dst.GetExtended() == GetExtAddress()));
316
317 LogDebg("Received frame in state (SubMac %s, CSL %s), timestamp %lu", StateToString(mState),
318 mIsCslSampling ? "CslSample" : "CslSleep",
319 ToUlong(static_cast<uint32_t>(aFrame->mInfo.mRxInfo.mTimestamp)));
320
321 VerifyOrExit(mState == kStateCslSample);
322
323 GetCslWindowEdges(ahead, after);
324 ahead -= kMinReceiveOnAhead + kCslReceiveTimeAhead;
325
326 sampleTime = mCslSampleTime.GetValue() - mCslPeriod * kUsPerTenSymbols;
327 deviation = aFrame->mInfo.mRxInfo.mTimestamp + kRadioHeaderPhrDuration - sampleTime;
328
329 // This logs three values (all in microseconds):
330 // - Absolute sample time in which the CSL receiver expected the MHR of the received frame.
331 // - Allowed margin around that time accounting for accuracy and uncertainty from both devices.
332 // - Real deviation on the reception of the MHR with regards to expected sample time. This can
333 // be due to clocks drift and/or CSL Phase rounding error.
334 // This means that a deviation absolute value greater than the margin would result in the frame
335 // not being received out of the debug mode.
336 logString.Append("Expected sample time %lu, margin ±%lu, deviation %ld", ToUlong(sampleTime), ToUlong(ahead),
337 static_cast<long>(deviation));
338
339 // Treat as a warning when the deviation is not within the margins. Neither kCslReceiveTimeAhead
340 // or kMinReceiveOnAhead/kMinReceiveOnAfter are considered for the margin since they have no
341 // impact on understanding possible deviation errors between transmitter and receiver. So in this
342 // case only `ahead` is used, as an allowable max deviation in both +/- directions.
343 if ((deviation + ahead > 0) && (deviation < static_cast<int32_t>(ahead)))
344 {
345 LogDebg("%s", logString.AsCString());
346 }
347 else
348 {
349 LogWarn("%s", logString.AsCString());
350 }
351
352 exit:
353 return;
354 }
355 #endif
356
HandleReceiveDone(RxFrame * aFrame,Error aError)357 void SubMac::HandleReceiveDone(RxFrame *aFrame, Error aError)
358 {
359 if (mPcapCallback.IsSet() && (aFrame != nullptr) && (aError == kErrorNone))
360 {
361 mPcapCallback.Invoke(aFrame, false);
362 }
363
364 if (!ShouldHandleTransmitSecurity() && aFrame != nullptr && aFrame->mInfo.mRxInfo.mAckedWithSecEnhAck)
365 {
366 SignalFrameCounterUsed(aFrame->mInfo.mRxInfo.mAckFrameCounter, aFrame->mInfo.mRxInfo.mAckKeyId);
367 }
368
369 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
370 if (aFrame != nullptr && aError == kErrorNone)
371 {
372 #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
373 LogReceived(aFrame);
374 #endif
375 // Assuming the risk of the parent missing the Enh-ACK in favor of smaller CSL receive window
376 if ((mCslPeriod > 0) && aFrame->mInfo.mRxInfo.mAckedWithSecEnhAck)
377 {
378 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
379 mCslLastSync = TimerMicro::GetNow();
380 #else
381 mCslLastSync = TimeMicro(static_cast<uint32_t>(aFrame->mInfo.mRxInfo.mTimestamp));
382 #endif
383 }
384 }
385 #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
386
387 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
388 if (!mRadioFilterEnabled)
389 #endif
390 {
391 mCallbacks.ReceiveDone(aFrame, aError);
392 }
393 }
394
Send(void)395 Error SubMac::Send(void)
396 {
397 Error error = kErrorNone;
398
399 switch (mState)
400 {
401 case kStateDisabled:
402 case kStateCsmaBackoff:
403 #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
404 case kStateCslTransmit:
405 #endif
406 case kStateTransmit:
407 #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
408 case kStateDelayBeforeRetx:
409 #endif
410 case kStateSleep:
411 case kStateReceive:
412 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
413 case kStateCslSample:
414 #endif
415 break;
416
417 case kStateEnergyScan:
418 ExitNow(error = kErrorInvalidState);
419 }
420
421 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
422 if (mRadioFilterEnabled)
423 {
424 mCallbacks.TransmitDone(mTransmitFrame, nullptr, mTransmitFrame.GetAckRequest() ? kErrorNoAck : kErrorNone);
425 ExitNow();
426 }
427 #endif
428
429 ProcessTransmitSecurity();
430
431 mCsmaBackoffs = 0;
432 mTransmitRetries = 0;
433
434 #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
435 mRetxDelayBackOffExponent = kRetxDelayMinBackoffExponent;
436 #endif
437
438 StartCsmaBackoff();
439
440 exit:
441 return error;
442 }
443
ProcessTransmitSecurity(void)444 void SubMac::ProcessTransmitSecurity(void)
445 {
446 const ExtAddress *extAddress = nullptr;
447 uint8_t keyIdMode;
448
449 VerifyOrExit(mTransmitFrame.GetSecurityEnabled());
450 VerifyOrExit(!mTransmitFrame.IsSecurityProcessed());
451
452 SuccessOrExit(mTransmitFrame.GetKeyIdMode(keyIdMode));
453
454 if (!mTransmitFrame.IsHeaderUpdated())
455 {
456 mTransmitFrame.SetKeyId(mKeyId);
457 }
458
459 VerifyOrExit(ShouldHandleTransmitSecurity());
460 VerifyOrExit(keyIdMode == Frame::kKeyIdMode1);
461
462 mTransmitFrame.SetAesKey(GetCurrentMacKey());
463
464 if (!mTransmitFrame.IsHeaderUpdated())
465 {
466 uint32_t frameCounter = GetFrameCounter();
467
468 mTransmitFrame.SetFrameCounter(frameCounter);
469 SignalFrameCounterUsed(frameCounter, mKeyId);
470 }
471
472 extAddress = &GetExtAddress();
473
474 #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
475 // Transmit security will be processed after time IE content is updated.
476 VerifyOrExit(mTransmitFrame.GetTimeIeOffset() == 0);
477 #endif
478
479 mTransmitFrame.ProcessTransmitAesCcm(*extAddress);
480
481 exit:
482 return;
483 }
484
StartCsmaBackoff(void)485 void SubMac::StartCsmaBackoff(void)
486 {
487 uint8_t backoffExponent = kCsmaMinBe + mCsmaBackoffs;
488
489 #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
490 if (mTransmitFrame.mInfo.mTxInfo.mTxDelay != 0)
491 {
492 SetState(kStateCslTransmit);
493
494 if (ShouldHandleTransmitTargetTime())
495 {
496 if (Time(static_cast<uint32_t>(otPlatRadioGetNow(&GetInstance()))) <
497 Time(mTransmitFrame.mInfo.mTxInfo.mTxDelayBaseTime) + mTransmitFrame.mInfo.mTxInfo.mTxDelay -
498 kCcaSampleInterval - kCslTransmitTimeAhead - kRadioHeaderShrDuration)
499 {
500 mTimer.StartAt(Time(mTransmitFrame.mInfo.mTxInfo.mTxDelayBaseTime) - kCcaSampleInterval -
501 kCslTransmitTimeAhead - kRadioHeaderShrDuration,
502 mTransmitFrame.mInfo.mTxInfo.mTxDelay);
503 }
504 else // Transmit without delay
505 {
506 BeginTransmit();
507 }
508 }
509 else
510 {
511 BeginTransmit();
512 }
513
514 ExitNow();
515 }
516 #endif // !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
517
518 SetState(kStateCsmaBackoff);
519
520 VerifyOrExit(ShouldHandleCsmaBackOff(), BeginTransmit());
521
522 backoffExponent = Min(backoffExponent, kCsmaMaxBe);
523
524 StartTimerForBackoff(backoffExponent);
525
526 exit:
527 return;
528 }
529
StartTimerForBackoff(uint8_t aBackoffExponent)530 void SubMac::StartTimerForBackoff(uint8_t aBackoffExponent)
531 {
532 uint32_t backoff;
533
534 backoff = Random::NonCrypto::GetUint32InRange(0, static_cast<uint32_t>(1UL << aBackoffExponent));
535 backoff *= (kUnitBackoffPeriod * Radio::kSymbolTime);
536
537 if (mRxOnWhenIdle)
538 {
539 IgnoreError(Get<Radio>().Receive(mTransmitFrame.GetChannel()));
540 }
541 else
542 {
543 IgnoreError(Get<Radio>().Sleep());
544 }
545
546 #if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE
547 mTimer.Start(backoff);
548 #else
549 mTimer.Start(backoff / 1000UL);
550 #endif
551
552 #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
553 if (mState == kStateDelayBeforeRetx)
554 {
555 LogDebg("Delaying retx for %lu usec (be=%u)", ToUlong(backoff), aBackoffExponent);
556 }
557 #endif
558 }
559
BeginTransmit(void)560 void SubMac::BeginTransmit(void)
561 {
562 Error error;
563
564 #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
565 VerifyOrExit(mState == kStateCsmaBackoff || mState == kStateCslTransmit);
566 #else
567 VerifyOrExit(mState == kStateCsmaBackoff);
568 #endif
569
570 if ((mRadioCaps & OT_RADIO_CAPS_SLEEP_TO_TX) == 0)
571 {
572 SuccessOrAssert(Get<Radio>().Receive(mTransmitFrame.GetChannel()));
573 }
574
575 SetState(kStateTransmit);
576
577 if (mPcapCallback.IsSet())
578 {
579 mPcapCallback.Invoke(&mTransmitFrame, true);
580 }
581
582 error = Get<Radio>().Transmit(mTransmitFrame);
583
584 if (error == kErrorInvalidState && mTransmitFrame.mInfo.mTxInfo.mTxDelay > 0)
585 {
586 // Platform `transmit_at` fails and we send the frame directly.
587 mTransmitFrame.mInfo.mTxInfo.mTxDelay = 0;
588 mTransmitFrame.mInfo.mTxInfo.mTxDelayBaseTime = 0;
589
590 error = Get<Radio>().Transmit(mTransmitFrame);
591 }
592
593 SuccessOrAssert(error);
594
595 exit:
596 return;
597 }
598
HandleTransmitStarted(TxFrame & aFrame)599 void SubMac::HandleTransmitStarted(TxFrame &aFrame)
600 {
601 if (ShouldHandleAckTimeout() && aFrame.GetAckRequest())
602 {
603 #if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE
604 mTimer.Start(kAckTimeout * 1000UL);
605 #else
606 mTimer.Start(kAckTimeout);
607 #endif
608 }
609 }
610
HandleTransmitDone(TxFrame & aFrame,RxFrame * aAckFrame,Error aError)611 void SubMac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError)
612 {
613 bool ccaSuccess = true;
614 bool shouldRetx;
615
616 // Stop ack timeout timer.
617
618 mTimer.Stop();
619
620 // Record CCA success or failure status.
621
622 switch (aError)
623 {
624 case kErrorAbort:
625 // Do not record CCA status in case of `ABORT` error
626 // since there may be no CCA check performed by radio.
627 break;
628
629 case kErrorChannelAccessFailure:
630 ccaSuccess = false;
631
632 OT_FALL_THROUGH;
633
634 case kErrorNone:
635 case kErrorNoAck:
636 if (aFrame.IsCsmaCaEnabled())
637 {
638 mCallbacks.RecordCcaStatus(ccaSuccess, aFrame.GetChannel());
639 }
640 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
641 // Actual synchronization timestamp should be from the sent frame instead of the current time.
642 // Assuming the error here since it is bounded and has very small effect on the final window duration.
643 if (aAckFrame != nullptr && aFrame.GetHeaderIe(CslIe::kHeaderIeId) != nullptr)
644 {
645 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
646 mCslLastSync = TimerMicro::GetNow();
647 #else
648 mCslLastSync = TimeMicro(static_cast<uint32_t>(otPlatRadioGetNow(&GetInstance())));
649 #endif
650 }
651 #endif
652 break;
653
654 default:
655 OT_ASSERT(false);
656 OT_UNREACHABLE_CODE(ExitNow());
657 }
658
659 SignalFrameCounterUsedOnTxDone(aFrame);
660
661 // Determine whether a CSMA retry is required.
662
663 if (!ccaSuccess && ShouldHandleCsmaBackOff() && mCsmaBackoffs < aFrame.GetMaxCsmaBackoffs())
664 {
665 mCsmaBackoffs++;
666 StartCsmaBackoff();
667 ExitNow();
668 }
669
670 mCsmaBackoffs = 0;
671
672 // Determine whether to re-transmit the frame.
673
674 shouldRetx = ((aError != kErrorNone) && ShouldHandleRetries() && (mTransmitRetries < aFrame.GetMaxFrameRetries()));
675
676 mCallbacks.RecordFrameTransmitStatus(aFrame, aError, mTransmitRetries, shouldRetx);
677
678 if (shouldRetx)
679 {
680 mTransmitRetries++;
681 aFrame.SetIsARetransmission(true);
682
683 #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
684 if (aError == kErrorNoAck)
685 {
686 SetState(kStateDelayBeforeRetx);
687 StartTimerForBackoff(mRetxDelayBackOffExponent);
688 mRetxDelayBackOffExponent =
689 Min(static_cast<uint8_t>(mRetxDelayBackOffExponent + 1), kRetxDelayMaxBackoffExponent);
690 ExitNow();
691 }
692 #endif
693
694 StartCsmaBackoff();
695 ExitNow();
696 }
697
698 SetState(kStateReceive);
699
700 #if OPENTHREAD_RADIO
701 if (aFrame.GetChannel() != aFrame.GetRxChannelAfterTxDone())
702 {
703 // On RCP build, we switch immediately to the specified RX
704 // channel if it is different from the channel on which frame
705 // was sent. On FTD or MTD builds we don't need to do
706 // the same as the `Mac` will switch the channel from the
707 // `mCallbacks.TransmitDone()`.
708
709 IgnoreError(Get<Radio>().Receive(aFrame.GetRxChannelAfterTxDone()));
710 }
711 #endif
712
713 mCallbacks.TransmitDone(aFrame, aAckFrame, aError);
714
715 exit:
716 return;
717 }
718
SignalFrameCounterUsedOnTxDone(const TxFrame & aFrame)719 void SubMac::SignalFrameCounterUsedOnTxDone(const TxFrame &aFrame)
720 {
721 uint8_t keyIdMode;
722 uint8_t keyId;
723 uint32_t frameCounter;
724 bool allowError = false;
725
726 OT_UNUSED_VARIABLE(allowError);
727
728 VerifyOrExit(!ShouldHandleTransmitSecurity() && aFrame.GetSecurityEnabled() && aFrame.IsHeaderUpdated());
729
730 // In an FTD/MTD build, if/when link-raw is enabled, the `TxFrame`
731 // is prepared and given by user and may not necessarily follow 15.4
732 // frame format (link raw can be used with vendor-specific format),
733 // so we allow failure when parsing the frame (i.e., do not assert
734 // on an error). In other cases (in an RCP build or in an FTD/MTD
735 // build without link-raw) since the `TxFrame` should be prepared by
736 // OpenThread core, we expect no error and therefore assert if
737 // parsing fails.
738
739 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
740 allowError = Get<LinkRaw>().IsEnabled();
741 #endif
742
743 VerifyOrExit(aFrame.GetKeyIdMode(keyIdMode) == kErrorNone, OT_ASSERT(allowError));
744 VerifyOrExit(keyIdMode == Frame::kKeyIdMode1);
745
746 VerifyOrExit(aFrame.GetFrameCounter(frameCounter) == kErrorNone, OT_ASSERT(allowError));
747 VerifyOrExit(aFrame.GetKeyId(keyId) == kErrorNone, OT_ASSERT(allowError));
748
749 SignalFrameCounterUsed(frameCounter, keyId);
750
751 exit:
752 return;
753 }
754
GetRssi(void) const755 int8_t SubMac::GetRssi(void) const
756 {
757 int8_t rssi;
758
759 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
760 if (mRadioFilterEnabled)
761 {
762 rssi = Radio::kInvalidRssi;
763 }
764 else
765 #endif
766 {
767 rssi = Get<Radio>().GetRssi();
768 }
769
770 return rssi;
771 }
772
GetNoiseFloor(void) const773 int8_t SubMac::GetNoiseFloor(void) const { return Get<Radio>().GetReceiveSensitivity(); }
774
EnergyScan(uint8_t aScanChannel,uint16_t aScanDuration)775 Error SubMac::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration)
776 {
777 Error error = kErrorNone;
778
779 switch (mState)
780 {
781 case kStateDisabled:
782 case kStateCsmaBackoff:
783 case kStateTransmit:
784 #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
785 case kStateCslTransmit:
786 #endif
787 #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
788 case kStateDelayBeforeRetx:
789 #endif
790 case kStateEnergyScan:
791 ExitNow(error = kErrorInvalidState);
792
793 case kStateReceive:
794 case kStateSleep:
795 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
796 case kStateCslSample:
797 #endif
798 break;
799 }
800
801 #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
802 VerifyOrExit(!mRadioFilterEnabled, HandleEnergyScanDone(Radio::kInvalidRssi));
803 #endif
804
805 if (RadioSupportsEnergyScan())
806 {
807 IgnoreError(Get<Radio>().EnergyScan(aScanChannel, aScanDuration));
808 SetState(kStateEnergyScan);
809 }
810 else if (ShouldHandleEnergyScan())
811 {
812 SuccessOrAssert(Get<Radio>().Receive(aScanChannel));
813
814 SetState(kStateEnergyScan);
815 mEnergyScanMaxRssi = Radio::kInvalidRssi;
816 mEnergyScanEndTime = TimerMilli::GetNow() + static_cast<uint32_t>(aScanDuration);
817 mTimer.Start(0);
818 }
819 else
820 {
821 error = kErrorNotImplemented;
822 }
823
824 exit:
825 return error;
826 }
827
SampleRssi(void)828 void SubMac::SampleRssi(void)
829 {
830 OT_ASSERT(!RadioSupportsEnergyScan());
831
832 int8_t rssi = GetRssi();
833
834 if (rssi != Radio::kInvalidRssi)
835 {
836 if ((mEnergyScanMaxRssi == Radio::kInvalidRssi) || (rssi > mEnergyScanMaxRssi))
837 {
838 mEnergyScanMaxRssi = rssi;
839 }
840 }
841
842 if (TimerMilli::GetNow() < mEnergyScanEndTime)
843 {
844 #if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE
845 mTimer.StartAt(mTimer.GetFireTime(), kEnergyScanRssiSampleInterval * 1000UL);
846 #else
847 mTimer.StartAt(mTimer.GetFireTime(), kEnergyScanRssiSampleInterval);
848 #endif
849 }
850 else
851 {
852 HandleEnergyScanDone(mEnergyScanMaxRssi);
853 }
854 }
855
HandleEnergyScanDone(int8_t aMaxRssi)856 void SubMac::HandleEnergyScanDone(int8_t aMaxRssi)
857 {
858 SetState(kStateReceive);
859 mCallbacks.EnergyScanDone(aMaxRssi);
860 }
861
HandleTimer(void)862 void SubMac::HandleTimer(void)
863 {
864 switch (mState)
865 {
866 #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
867 case kStateCslTransmit:
868 BeginTransmit();
869 break;
870 #endif
871 case kStateCsmaBackoff:
872 BeginTransmit();
873 break;
874
875 case kStateTransmit:
876 LogDebg("Ack timer timed out");
877 IgnoreError(Get<Radio>().Receive(mTransmitFrame.GetChannel()));
878 HandleTransmitDone(mTransmitFrame, nullptr, kErrorNoAck);
879 break;
880
881 #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
882 case kStateDelayBeforeRetx:
883 StartCsmaBackoff();
884 break;
885 #endif
886
887 case kStateEnergyScan:
888 SampleRssi();
889 break;
890
891 default:
892 break;
893 }
894 }
895
ShouldHandleTransmitSecurity(void) const896 bool SubMac::ShouldHandleTransmitSecurity(void) const
897 {
898 bool swTxSecurity = true;
899
900 VerifyOrExit(!RadioSupportsTransmitSecurity(), swTxSecurity = false);
901
902 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
903 VerifyOrExit(Get<LinkRaw>().IsEnabled());
904 #endif
905
906 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE || OPENTHREAD_RADIO
907 swTxSecurity = OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE;
908 #endif
909
910 exit:
911 return swTxSecurity;
912 }
913
ShouldHandleCsmaBackOff(void) const914 bool SubMac::ShouldHandleCsmaBackOff(void) const
915 {
916 bool swCsma = true;
917
918 VerifyOrExit(!RadioSupportsCsmaBackoff(), swCsma = false);
919
920 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
921 VerifyOrExit(Get<LinkRaw>().IsEnabled());
922 #endif
923
924 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE || OPENTHREAD_RADIO
925 swCsma = OPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE;
926 #endif
927
928 exit:
929 return swCsma;
930 }
931
ShouldHandleAckTimeout(void) const932 bool SubMac::ShouldHandleAckTimeout(void) const
933 {
934 bool swAckTimeout = true;
935
936 VerifyOrExit(!RadioSupportsAckTimeout(), swAckTimeout = false);
937
938 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
939 VerifyOrExit(Get<LinkRaw>().IsEnabled());
940 #endif
941
942 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE || OPENTHREAD_RADIO
943 swAckTimeout = OPENTHREAD_CONFIG_MAC_SOFTWARE_ACK_TIMEOUT_ENABLE;
944 #endif
945
946 exit:
947 return swAckTimeout;
948 }
949
ShouldHandleRetries(void) const950 bool SubMac::ShouldHandleRetries(void) const
951 {
952 bool swRetries = true;
953
954 VerifyOrExit(!RadioSupportsRetries(), swRetries = false);
955
956 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
957 VerifyOrExit(Get<LinkRaw>().IsEnabled());
958 #endif
959
960 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE || OPENTHREAD_RADIO
961 swRetries = OPENTHREAD_CONFIG_MAC_SOFTWARE_RETRANSMIT_ENABLE;
962 #endif
963
964 exit:
965 return swRetries;
966 }
967
ShouldHandleEnergyScan(void) const968 bool SubMac::ShouldHandleEnergyScan(void) const
969 {
970 bool swEnergyScan = true;
971
972 VerifyOrExit(!RadioSupportsEnergyScan(), swEnergyScan = false);
973
974 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
975 VerifyOrExit(Get<LinkRaw>().IsEnabled());
976 #endif
977
978 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE || OPENTHREAD_RADIO
979 swEnergyScan = OPENTHREAD_CONFIG_MAC_SOFTWARE_ENERGY_SCAN_ENABLE;
980 #endif
981
982 exit:
983 return swEnergyScan;
984 }
985
ShouldHandleTransmitTargetTime(void) const986 bool SubMac::ShouldHandleTransmitTargetTime(void) const
987 {
988 bool swTxDelay = true;
989
990 VerifyOrExit(!RadioSupportsTransmitTiming(), swTxDelay = false);
991
992 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE
993 VerifyOrExit(Get<LinkRaw>().IsEnabled());
994 #endif
995
996 #if OPENTHREAD_CONFIG_LINK_RAW_ENABLE || OPENTHREAD_RADIO
997 swTxDelay = OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE;
998 #endif
999
1000 exit:
1001 return swTxDelay;
1002 }
1003
ShouldHandleTransitionToSleep(void) const1004 bool SubMac::ShouldHandleTransitionToSleep(void) const { return (mRxOnWhenIdle || !RadioSupportsRxOnWhenIdle()); }
1005
SetState(State aState)1006 void SubMac::SetState(State aState)
1007 {
1008 if (mState != aState)
1009 {
1010 LogDebg("RadioState: %s -> %s", StateToString(mState), StateToString(aState));
1011 mState = aState;
1012 }
1013 }
1014
SetMacKey(uint8_t aKeyIdMode,uint8_t aKeyId,const KeyMaterial & aPrevKey,const KeyMaterial & aCurrKey,const KeyMaterial & aNextKey)1015 void SubMac::SetMacKey(uint8_t aKeyIdMode,
1016 uint8_t aKeyId,
1017 const KeyMaterial &aPrevKey,
1018 const KeyMaterial &aCurrKey,
1019 const KeyMaterial &aNextKey)
1020 {
1021 switch (aKeyIdMode)
1022 {
1023 case Frame::kKeyIdMode0:
1024 case Frame::kKeyIdMode2:
1025 break;
1026 case Frame::kKeyIdMode1:
1027 mKeyId = aKeyId;
1028 mPrevKey = aPrevKey;
1029 mCurrKey = aCurrKey;
1030 mNextKey = aNextKey;
1031 break;
1032
1033 default:
1034 OT_ASSERT(false);
1035 break;
1036 }
1037
1038 VerifyOrExit(!ShouldHandleTransmitSecurity());
1039
1040 Get<Radio>().SetMacKey(aKeyIdMode, aKeyId, aPrevKey, aCurrKey, aNextKey);
1041
1042 exit:
1043 return;
1044 }
1045
SignalFrameCounterUsed(uint32_t aFrameCounter,uint8_t aKeyId)1046 void SubMac::SignalFrameCounterUsed(uint32_t aFrameCounter, uint8_t aKeyId)
1047 {
1048 VerifyOrExit(aKeyId == mKeyId);
1049
1050 mCallbacks.FrameCounterUsed(aFrameCounter);
1051
1052 // It not always guaranteed that this method is invoked in order
1053 // for different counter values (i.e., we may get it for a
1054 // smaller counter value after a lager one). This may happen due
1055 // to a new counter value being used for an enhanced-ack during
1056 // tx of a frame. Note that the newer counter used for enhanced-ack
1057 // is processed from `HandleReceiveDone()` which can happen before
1058 // processing of the older counter value from `HandleTransmitDone()`.
1059
1060 VerifyOrExit(mFrameCounter <= aFrameCounter);
1061 mFrameCounter = aFrameCounter + 1;
1062
1063 exit:
1064 return;
1065 }
1066
SetFrameCounter(uint32_t aFrameCounter,bool aSetIfLarger)1067 void SubMac::SetFrameCounter(uint32_t aFrameCounter, bool aSetIfLarger)
1068 {
1069 if (!aSetIfLarger || (aFrameCounter > mFrameCounter))
1070 {
1071 mFrameCounter = aFrameCounter;
1072 }
1073
1074 VerifyOrExit(!ShouldHandleTransmitSecurity());
1075
1076 if (aSetIfLarger)
1077 {
1078 Get<Radio>().SetMacFrameCounterIfLarger(aFrameCounter);
1079 }
1080 else
1081 {
1082 Get<Radio>().SetMacFrameCounter(aFrameCounter);
1083 }
1084
1085 exit:
1086 return;
1087 }
1088
1089 // LCOV_EXCL_START
1090
StateToString(State aState)1091 const char *SubMac::StateToString(State aState)
1092 {
1093 static const char *const kStateStrings[] = {
1094 "Disabled", // (0) kStateDisabled
1095 "Sleep", // (1) kStateSleep
1096 "Receive", // (2) kStateReceive
1097 "CsmaBackoff", // (3) kStateCsmaBackoff
1098 "Transmit", // (4) kStateTransmit
1099 "EnergyScan", // (5) kStateEnergyScan
1100 #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
1101 "DelayBeforeRetx", // (6) kStateDelayBeforeRetx
1102 #endif
1103 #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
1104 "CslTransmit", // (7) kStateCslTransmit
1105 #endif
1106 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
1107 "CslSample", // (8) kStateCslSample
1108 #endif
1109 };
1110
1111 static_assert(kStateDisabled == 0, "kStateDisabled value is not correct");
1112 static_assert(kStateSleep == 1, "kStateSleep value is not correct");
1113 static_assert(kStateReceive == 2, "kStateReceive value is not correct");
1114 static_assert(kStateCsmaBackoff == 3, "kStateCsmaBackoff value is not correct");
1115 static_assert(kStateTransmit == 4, "kStateTransmit value is not correct");
1116 static_assert(kStateEnergyScan == 5, "kStateEnergyScan value is not correct");
1117
1118 #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY
1119 static_assert(kStateDelayBeforeRetx == 6, "kStateDelayBeforeRetx value is not correct");
1120 #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
1121 static_assert(kStateCslTransmit == 7, "kStateCslTransmit value is not correct");
1122 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
1123 static_assert(kStateCslSample == 8, "kStateCslSample value is not correct");
1124 #endif
1125 #elif OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
1126 static_assert(kStateCslSample == 7, "kStateCslSample value is not correct");
1127 #endif
1128
1129 #elif !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
1130 static_assert(kStateCslTransmit == 6, "kStateCslTransmit value is not correct");
1131 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
1132 static_assert(kStateCslSample == 7, "kStateCslSample value is not correct");
1133 #endif
1134 #elif OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
1135 static_assert(kStateCslSample == 6, "kStateCslSample value is not correct");
1136 #endif
1137
1138 return kStateStrings[aState];
1139 }
1140
1141 // LCOV_EXCL_STOP
1142
1143 //---------------------------------------------------------------------------------------------------------------------
1144 // CSL Receiver methods
1145
1146 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
UpdateCsl(uint16_t aPeriod,uint8_t aChannel,otShortAddress aShortAddr,const otExtAddress * aExtAddr)1147 bool SubMac::UpdateCsl(uint16_t aPeriod, uint8_t aChannel, otShortAddress aShortAddr, const otExtAddress *aExtAddr)
1148 {
1149 bool diffPeriod = aPeriod != mCslPeriod;
1150 bool diffChannel = aChannel != mCslChannel;
1151 bool diffPeer = aShortAddr != mCslPeerShort;
1152 bool retval = diffPeriod || diffChannel || diffPeer;
1153
1154 VerifyOrExit(retval);
1155 mCslChannel = aChannel;
1156
1157 VerifyOrExit(diffPeriod || diffPeer);
1158 mCslPeriod = aPeriod;
1159 mCslPeerShort = aShortAddr;
1160 IgnoreError(Get<Radio>().EnableCsl(aPeriod, aShortAddr, aExtAddr));
1161
1162 mCslTimer.Stop();
1163 if (mCslPeriod > 0)
1164 {
1165 mCslSampleTime = TimeMicro(static_cast<uint32_t>(otPlatRadioGetNow(&GetInstance())));
1166 mIsCslSampling = false;
1167 HandleCslTimer();
1168 }
1169
1170 exit:
1171 return retval;
1172 }
1173
HandleCslTimer(Timer & aTimer)1174 void SubMac::HandleCslTimer(Timer &aTimer) { aTimer.Get<SubMac>().HandleCslTimer(); }
1175
HandleCslTimer(void)1176 void SubMac::HandleCslTimer(void)
1177 {
1178 /*
1179 * CSL sample timing diagram
1180 * |<---------------------------------Sample--------------------------------->|<--------Sleep--------->|
1181 * | | |
1182 * |<--Ahead-->|<--UnCert-->|<--Drift-->|<--Drift-->|<--UnCert-->|<--MinWin-->| |
1183 * | | | | | | | |
1184 * ---|-----------|------------|-----------|-----------|------------|------------|----------//------------|---
1185 * -timeAhead CslPhase +timeAfter -timeAhead
1186 *
1187 * The handler works in different ways when the radio supports receive-timing and doesn't.
1188 *
1189 * When the radio supports receive-timing:
1190 * The handler will be called once per CSL period. When the handler is called, it will set the timer to
1191 * fire at the next CSL sample time and call `Radio::ReceiveAt` to start sampling for the current CSL period.
1192 * The timer fires some time before the actual sample time. After `Radio::ReceiveAt` is called, the radio will
1193 * remain in sleep state until the actual sample time.
1194 * Note that it never call `Radio::Sleep` explicitly. The radio will fall into sleep after `ReceiveAt` ends. This
1195 * will be done by the platform as part of the `otPlatRadioReceiveAt` API.
1196 *
1197 * Timer fires Timer fires
1198 * ^ ^
1199 * x-|------------|-------------------------------------x-|------------|---------------------------------------|
1200 * sample sleep sample sleep
1201 *
1202 * When the radio doesn't support receive-timing:
1203 * The handler will be called twice per CSL period: at the beginning of sample and sleep. When the handler is
1204 * called, it will explicitly change the radio state due to the current state by calling `Radio::Receive` or
1205 * `Radio::Sleep`.
1206 *
1207 * Timer fires Timer fires Timer fires Timer fires
1208 * ^ ^ ^ ^
1209 * |------------|---------------------------------------|------------|---------------------------------------|
1210 * sample sleep sample sleep
1211 *
1212 */
1213 uint32_t periodUs = mCslPeriod * kUsPerTenSymbols;
1214 uint32_t timeAhead, timeAfter, winStart, winDuration;
1215
1216 GetCslWindowEdges(timeAhead, timeAfter);
1217
1218 if (mIsCslSampling)
1219 {
1220 mIsCslSampling = false;
1221 mCslTimer.FireAt(mCslSampleTime - timeAhead);
1222 if (mState == kStateCslSample)
1223 {
1224 #if !OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE
1225 IgnoreError(Get<Radio>().Sleep()); // Don't actually sleep for debugging
1226 #endif
1227 LogDebg("CSL sleep %lu", ToUlong(mCslTimer.GetNow().GetValue()));
1228 }
1229 }
1230 else
1231 {
1232 if (RadioSupportsReceiveTiming())
1233 {
1234 mCslTimer.FireAt(mCslSampleTime - timeAhead + periodUs);
1235 timeAhead -= kCslReceiveTimeAhead;
1236 winStart = mCslSampleTime.GetValue() - timeAhead;
1237 }
1238 else
1239 {
1240 mCslTimer.FireAt(mCslSampleTime + timeAfter);
1241 mIsCslSampling = true;
1242 winStart = ot::TimerMicro::GetNow().GetValue();
1243 }
1244
1245 winDuration = timeAhead + timeAfter;
1246 mCslSampleTime += periodUs;
1247
1248 Get<Radio>().UpdateCslSampleTime(mCslSampleTime.GetValue());
1249
1250 // Schedule reception window for any state except RX - so that CSL RX Window has lower priority
1251 // than scanning or RX after the data poll.
1252 if (RadioSupportsReceiveTiming() && (mState != kStateDisabled) && (mState != kStateReceive))
1253 {
1254 IgnoreError(Get<Radio>().ReceiveAt(mCslChannel, winStart, winDuration));
1255 }
1256 else if (mState == kStateCslSample)
1257 {
1258 IgnoreError(Get<Radio>().Receive(mCslChannel));
1259 }
1260
1261 LogDebg("CSL window start %lu, duration %lu", ToUlong(winStart), ToUlong(winDuration));
1262 }
1263 }
1264
GetCslWindowEdges(uint32_t & aAhead,uint32_t & aAfter)1265 void SubMac::GetCslWindowEdges(uint32_t &aAhead, uint32_t &aAfter)
1266 {
1267 uint32_t semiPeriod = mCslPeriod * kUsPerTenSymbols / 2;
1268 uint32_t curTime, elapsed, semiWindow;
1269
1270 #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_LOCAL_TIME_SYNC
1271 curTime = TimerMicro::GetNow().GetValue();
1272 #else
1273 curTime = static_cast<uint32_t>(otPlatRadioGetNow(&GetInstance()));
1274 #endif
1275
1276 elapsed = curTime - mCslLastSync.GetValue();
1277
1278 semiWindow =
1279 static_cast<uint32_t>(static_cast<uint64_t>(elapsed) *
1280 (Get<Radio>().GetCslAccuracy() + mCslParentAccuracy.GetClockAccuracy()) / 1000000);
1281 semiWindow += mCslParentAccuracy.GetUncertaintyInMicrosec() + Get<Radio>().GetCslUncertainty() * 10;
1282
1283 aAhead = Min(semiPeriod, semiWindow + kMinReceiveOnAhead + kCslReceiveTimeAhead);
1284 aAfter = Min(semiPeriod, semiWindow + kMinReceiveOnAfter);
1285 }
1286 #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
1287
1288 } // namespace Mac
1289 } // namespace ot
1290