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