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