1 /*
2 * Copyright (c) 2020, 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 #include "csl_tx_scheduler.hpp"
30
31 #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
32
33 #include "common/locator_getters.hpp"
34 #include "common/log.hpp"
35 #include "common/time.hpp"
36 #include "mac/mac.hpp"
37
38 namespace ot {
39
40 RegisterLogModule("CslTxScheduler");
41
Callbacks(Instance & aInstance)42 CslTxScheduler::Callbacks::Callbacks(Instance &aInstance)
43 : InstanceLocator(aInstance)
44 {
45 }
46
PrepareFrameForChild(Mac::TxFrame & aFrame,FrameContext & aContext,Child & aChild)47 inline Error CslTxScheduler::Callbacks::PrepareFrameForChild(Mac::TxFrame &aFrame,
48 FrameContext &aContext,
49 Child &aChild)
50 {
51 return Get<IndirectSender>().PrepareFrameForChild(aFrame, aContext, aChild);
52 }
53
HandleSentFrameToChild(const Mac::TxFrame & aFrame,const FrameContext & aContext,Error aError,Child & aChild)54 inline void CslTxScheduler::Callbacks::HandleSentFrameToChild(const Mac::TxFrame &aFrame,
55 const FrameContext &aContext,
56 Error aError,
57 Child &aChild)
58 {
59 Get<IndirectSender>().HandleSentFrameToChild(aFrame, aContext, aError, aChild);
60 }
61
62 //---------------------------------------------------------
63
CslTxScheduler(Instance & aInstance)64 CslTxScheduler::CslTxScheduler(Instance &aInstance)
65 : InstanceLocator(aInstance)
66 , mCslTxChild(nullptr)
67 , mCslTxMessage(nullptr)
68 , mFrameContext()
69 , mCallbacks(aInstance)
70 {
71 InitFrameRequestAhead();
72 }
73
InitFrameRequestAhead(void)74 void CslTxScheduler::InitFrameRequestAhead(void)
75 {
76 uint32_t busSpeedHz = otPlatRadioGetBusSpeed(&GetInstance());
77 // longest frame on bus is 127 bytes with some metadata, use 150 bytes for bus Tx time estimation
78 uint32_t busTxTimeUs = ((busSpeedHz == 0) ? 0 : (150 * 8 * 1000000 + busSpeedHz - 1) / busSpeedHz);
79
80 mCslFrameRequestAheadUs = OPENTHREAD_CONFIG_MAC_CSL_REQUEST_AHEAD_US + busTxTimeUs;
81 }
82
Update(void)83 void CslTxScheduler::Update(void)
84 {
85 if (mCslTxMessage == nullptr)
86 {
87 RescheduleCslTx();
88 }
89 else if ((mCslTxChild != nullptr) && (mCslTxChild->GetIndirectMessage() != mCslTxMessage))
90 {
91 // `Mac` has already started the CSL tx, so wait for tx done callback
92 // to call `RescheduleCslTx`
93 mCslTxChild->ResetCslTxAttempts();
94 mCslTxChild = nullptr;
95 mFrameContext.mMessageNextOffset = 0;
96 }
97 }
98
Clear(void)99 void CslTxScheduler::Clear(void)
100 {
101 for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
102 {
103 child.ResetCslTxAttempts();
104 child.SetCslSynchronized(false);
105 child.SetCslChannel(0);
106 child.SetCslTimeout(0);
107 child.SetCslPeriod(0);
108 child.SetCslPhase(0);
109 child.SetCslLastHeard(TimeMilli(0));
110 }
111
112 mFrameContext.mMessageNextOffset = 0;
113 mCslTxChild = nullptr;
114 mCslTxMessage = nullptr;
115 }
116
117 /**
118 * Always finds the most recent CSL tx among all children,
119 * and requests `Mac` to do CSL tx at specific time. It shouldn't be called
120 * when `Mac` is already starting to do the CSL tx (indicated by `mCslTxMessage`).
121 *
122 */
RescheduleCslTx(void)123 void CslTxScheduler::RescheduleCslTx(void)
124 {
125 uint32_t minDelayTime = Time::kMaxDuration;
126 Child *bestChild = nullptr;
127
128 for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
129 {
130 uint32_t delay;
131 uint32_t cslTxDelay;
132
133 if (!child.IsCslSynchronized() || child.GetIndirectMessageCount() == 0)
134 {
135 continue;
136 }
137
138 delay = GetNextCslTransmissionDelay(child, cslTxDelay, mCslFrameRequestAheadUs);
139
140 if (delay < minDelayTime)
141 {
142 minDelayTime = delay;
143 bestChild = &child;
144 }
145 }
146
147 if (bestChild != nullptr)
148 {
149 Get<Mac::Mac>().RequestCslFrameTransmission(minDelayTime / 1000UL);
150 }
151
152 mCslTxChild = bestChild;
153 }
154
GetNextCslTransmissionDelay(const Child & aChild,uint32_t & aDelayFromLastRx,uint32_t aAheadUs) const155 uint32_t CslTxScheduler::GetNextCslTransmissionDelay(const Child &aChild,
156 uint32_t &aDelayFromLastRx,
157 uint32_t aAheadUs) const
158 {
159 uint64_t radioNow = otPlatRadioGetNow(&GetInstance());
160 uint32_t periodInUs = aChild.GetCslPeriod() * kUsPerTenSymbols;
161
162 /* see CslTxScheduler::ChildInfo::mCslPhase */
163 uint64_t firstTxWindow = aChild.GetLastRxTimestamp() + aChild.GetCslPhase() * kUsPerTenSymbols;
164 uint64_t nextTxWindow = radioNow - (radioNow % periodInUs) + (firstTxWindow % periodInUs);
165
166 while (nextTxWindow < radioNow + aAheadUs)
167 {
168 nextTxWindow += periodInUs;
169 }
170
171 aDelayFromLastRx = static_cast<uint32_t>(nextTxWindow - aChild.GetLastRxTimestamp());
172
173 return static_cast<uint32_t>(nextTxWindow - radioNow - aAheadUs);
174 }
175
176 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
177
HandleFrameRequest(Mac::TxFrames & aTxFrames)178 Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &aTxFrames)
179 {
180 Mac::TxFrame *frame = nullptr;
181 uint32_t txDelay;
182 uint32_t delay;
183
184 VerifyOrExit(mCslTxChild != nullptr);
185 VerifyOrExit(mCslTxChild->IsCslSynchronized());
186
187 #if OPENTHREAD_CONFIG_MULTI_RADIO
188 frame = &aTxFrames.GetTxFrame(Mac::kRadioTypeIeee802154);
189 #else
190 frame = &aTxFrames.GetTxFrame();
191 #endif
192
193 VerifyOrExit(mCallbacks.PrepareFrameForChild(*frame, mFrameContext, *mCslTxChild) == kErrorNone, frame = nullptr);
194 mCslTxMessage = mCslTxChild->GetIndirectMessage();
195 VerifyOrExit(mCslTxMessage != nullptr, frame = nullptr);
196
197 if (mCslTxChild->GetIndirectTxAttempts() > 0 || mCslTxChild->GetCslTxAttempts() > 0)
198 {
199 // For a re-transmission of an indirect frame to a sleepy
200 // child, we ensure to use the same frame counter, key id, and
201 // data sequence number as the previous attempt.
202
203 frame->SetIsARetransmission(true);
204 frame->SetSequence(mCslTxChild->GetIndirectDataSequenceNumber());
205
206 if (frame->GetSecurityEnabled())
207 {
208 frame->SetFrameCounter(mCslTxChild->GetIndirectFrameCounter());
209 frame->SetKeyId(mCslTxChild->GetIndirectKeyId());
210 }
211 }
212 else
213 {
214 frame->SetIsARetransmission(false);
215 }
216
217 frame->SetChannel(mCslTxChild->GetCslChannel() == 0 ? Get<Mac::Mac>().GetPanChannel()
218 : mCslTxChild->GetCslChannel());
219
220 if (frame->GetChannel() != Get<Mac::Mac>().GetPanChannel())
221 {
222 frame->SetRxChannelAfterTxDone(Get<Mac::Mac>().GetPanChannel());
223 }
224
225 delay = GetNextCslTransmissionDelay(*mCslTxChild, txDelay, /* aAheadUs */ 0);
226
227 // We make sure that delay is less than `mCslFrameRequestAheadUs`
228 // plus some guard time. Note that we used `mCslFrameRequestAheadUs`
229 // in `RescheduleCslTx()` when determining the next CSL delay to
230 // schedule CSL tx with `Mac` but here we calculate the delay with
231 // zero `aAheadUs`. All the timings are in usec but when passing
232 // delay to `Mac` we divide by `1000` (to convert to msec) which
233 // can round the value down and cause `Mac` to start operation a
234 // bit (some usec) earlier. This is covered by adding the guard
235 // time `kFramePreparationGuardInterval`.
236 //
237 // In general this check handles the case where `Mac` is busy with
238 // other operations and therefore late to start the CSL tx operation
239 // and by the time `HandleFrameRequest()` is invoked, we miss the
240 // current CSL window and move to the next window.
241
242 VerifyOrExit(delay <= mCslFrameRequestAheadUs + kFramePreparationGuardInterval, frame = nullptr);
243
244 frame->SetTxDelay(txDelay);
245 frame->SetTxDelayBaseTime(
246 static_cast<uint32_t>(mCslTxChild->GetLastRxTimestamp())); // Only LSB part of the time is required.
247 frame->SetCsmaCaEnabled(false);
248
249 exit:
250 return frame;
251 }
252
253 #else // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
254
HandleFrameRequest(Mac::TxFrames &)255 Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &) { return nullptr; }
256
257 #endif // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
258
HandleSentFrame(const Mac::TxFrame & aFrame,Error aError)259 void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError)
260 {
261 Child *child = mCslTxChild;
262
263 mCslTxMessage = nullptr;
264
265 VerifyOrExit(child != nullptr); // The result is no longer interested by upper layer
266
267 mCslTxChild = nullptr;
268
269 HandleSentFrame(aFrame, aError, *child);
270
271 exit:
272 RescheduleCslTx();
273 }
274
HandleSentFrame(const Mac::TxFrame & aFrame,Error aError,Child & aChild)275 void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, Child &aChild)
276 {
277 switch (aError)
278 {
279 case kErrorNone:
280 aChild.ResetCslTxAttempts();
281 aChild.ResetIndirectTxAttempts();
282 break;
283
284 case kErrorNoAck:
285 OT_ASSERT(!aFrame.GetSecurityEnabled() || aFrame.IsHeaderUpdated());
286
287 aChild.IncrementCslTxAttempts();
288 LogInfo("CSL tx to child %04x failed, attempt %d/%d", aChild.GetRloc16(), aChild.GetCslTxAttempts(),
289 kMaxCslTriggeredTxAttempts);
290
291 if (aChild.GetCslTxAttempts() >= kMaxCslTriggeredTxAttempts)
292 {
293 // CSL transmission attempts reach max, consider child out of sync
294 aChild.SetCslSynchronized(false);
295 aChild.ResetCslTxAttempts();
296 }
297
298 OT_FALL_THROUGH;
299
300 case kErrorChannelAccessFailure:
301 case kErrorAbort:
302
303 // Even if CSL tx attempts count reaches max, the message won't be
304 // dropped until indirect tx attempts count reaches max. So here it
305 // would set sequence number and schedule next CSL tx.
306
307 if (!aFrame.IsEmpty())
308 {
309 aChild.SetIndirectDataSequenceNumber(aFrame.GetSequence());
310
311 if (aFrame.GetSecurityEnabled() && aFrame.IsHeaderUpdated())
312 {
313 uint32_t frameCounter;
314 uint8_t keyId;
315
316 IgnoreError(aFrame.GetFrameCounter(frameCounter));
317 aChild.SetIndirectFrameCounter(frameCounter);
318
319 IgnoreError(aFrame.GetKeyId(keyId));
320 aChild.SetIndirectKeyId(keyId);
321 }
322 }
323
324 ExitNow();
325
326 default:
327 OT_ASSERT(false);
328 OT_UNREACHABLE_CODE(break);
329 }
330
331 mCallbacks.HandleSentFrameToChild(aFrame, mFrameContext, aError, aChild);
332
333 exit:
334 return;
335 }
336
337 } // namespace ot
338
339 #endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
340