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 = nullptr;
94 mFrameContext.mMessageNextOffset = 0;
95 }
96 }
97
Clear(void)98 void CslTxScheduler::Clear(void)
99 {
100 for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
101 {
102 child.ResetCslTxAttempts();
103 child.SetCslSynchronized(false);
104 child.SetCslChannel(0);
105 child.SetCslTimeout(0);
106 child.SetCslPeriod(0);
107 child.SetCslPhase(0);
108 child.SetCslLastHeard(TimeMilli(0));
109 }
110
111 mFrameContext.mMessageNextOffset = 0;
112 mCslTxChild = nullptr;
113 mCslTxMessage = nullptr;
114 }
115
116 /**
117 * This method always finds the most recent CSL tx among all children,
118 * and requests `Mac` to do CSL tx at specific time. It shouldn't be called
119 * when `Mac` is already starting to do the CSL tx (indicated by `mCslTxMessage`).
120 *
121 */
RescheduleCslTx(void)122 void CslTxScheduler::RescheduleCslTx(void)
123 {
124 uint32_t minDelayTime = Time::kMaxDuration;
125 Child * bestChild = nullptr;
126
127 for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
128 {
129 uint32_t delay;
130 uint32_t cslTxDelay;
131
132 if (!child.IsCslSynchronized() || child.GetIndirectMessageCount() == 0)
133 {
134 continue;
135 }
136
137 delay = GetNextCslTransmissionDelay(child, cslTxDelay);
138
139 if (delay < minDelayTime)
140 {
141 minDelayTime = delay;
142 bestChild = &child;
143 }
144 }
145
146 if (bestChild != nullptr)
147 {
148 Get<Mac::Mac>().RequestCslFrameTransmission(minDelayTime / 1000UL);
149 }
150
151 mCslTxChild = bestChild;
152 }
153
GetNextCslTransmissionDelay(const Child & aChild,uint32_t & aDelayFromLastRx) const154 uint32_t CslTxScheduler::GetNextCslTransmissionDelay(const Child &aChild, uint32_t &aDelayFromLastRx) const
155 {
156 uint64_t radioNow = otPlatRadioGetNow(&GetInstance());
157 uint32_t periodInUs = aChild.GetCslPeriod() * kUsPerTenSymbols;
158 uint64_t firstTxWindow = aChild.GetLastRxTimestamp() + aChild.GetCslPhase() * kUsPerTenSymbols;
159 uint64_t nextTxWindow = radioNow - (radioNow % periodInUs) + (firstTxWindow % periodInUs);
160
161 while (nextTxWindow < radioNow + mCslFrameRequestAheadUs) nextTxWindow += periodInUs;
162
163 aDelayFromLastRx = static_cast<uint32_t>(nextTxWindow - aChild.GetLastRxTimestamp());
164
165 return static_cast<uint32_t>(nextTxWindow - radioNow - mCslFrameRequestAheadUs);
166 }
167
168 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
169
HandleFrameRequest(Mac::TxFrames & aTxFrames)170 Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &aTxFrames)
171 {
172 Mac::TxFrame *frame = nullptr;
173 uint32_t txDelay;
174
175 VerifyOrExit(mCslTxChild != nullptr);
176
177 #if OPENTHREAD_CONFIG_MULTI_RADIO
178 frame = &aTxFrames.GetTxFrame(Mac::kRadioTypeIeee802154);
179 #else
180 frame = &aTxFrames.GetTxFrame();
181 #endif
182
183 VerifyOrExit(mCallbacks.PrepareFrameForChild(*frame, mFrameContext, *mCslTxChild) == kErrorNone, frame = nullptr);
184 mCslTxMessage = mCslTxChild->GetIndirectMessage();
185 VerifyOrExit(mCslTxMessage != nullptr, frame = nullptr);
186
187 if (mCslTxChild->GetIndirectTxAttempts() > 0 || mCslTxChild->GetCslTxAttempts() > 0)
188 {
189 // For a re-transmission of an indirect frame to a sleepy
190 // child, we ensure to use the same frame counter, key id, and
191 // data sequence number as the previous attempt.
192
193 frame->SetIsARetransmission(true);
194 frame->SetSequence(mCslTxChild->GetIndirectDataSequenceNumber());
195
196 if (frame->GetSecurityEnabled())
197 {
198 frame->SetFrameCounter(mCslTxChild->GetIndirectFrameCounter());
199 frame->SetKeyId(mCslTxChild->GetIndirectKeyId());
200 }
201 }
202 else
203 {
204 frame->SetIsARetransmission(false);
205 }
206
207 frame->SetChannel(mCslTxChild->GetCslChannel() == 0 ? Get<Mac::Mac>().GetPanChannel()
208 : mCslTxChild->GetCslChannel());
209
210 GetNextCslTransmissionDelay(*mCslTxChild, txDelay);
211 frame->SetTxDelay(txDelay);
212 frame->SetTxDelayBaseTime(
213 static_cast<uint32_t>(mCslTxChild->GetLastRxTimestamp())); // Only LSB part of the time is required.
214 frame->SetCsmaCaEnabled(false);
215
216 exit:
217 return frame;
218 }
219
220 #else // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
221
HandleFrameRequest(Mac::TxFrames &)222 Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &)
223 {
224 return nullptr;
225 }
226
227 #endif // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
228
HandleSentFrame(const Mac::TxFrame & aFrame,Error aError)229 void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError)
230 {
231 Child *child = mCslTxChild;
232
233 mCslTxMessage = nullptr;
234
235 VerifyOrExit(child != nullptr); // The result is no longer interested by upper layer
236
237 mCslTxChild = nullptr;
238
239 HandleSentFrame(aFrame, aError, *child);
240
241 exit:
242 return;
243 }
244
HandleSentFrame(const Mac::TxFrame & aFrame,Error aError,Child & aChild)245 void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, Child &aChild)
246 {
247 switch (aError)
248 {
249 case kErrorNone:
250 aChild.ResetCslTxAttempts();
251 aChild.ResetIndirectTxAttempts();
252 break;
253
254 case kErrorNoAck:
255 OT_ASSERT(!aFrame.GetSecurityEnabled() || aFrame.IsHeaderUpdated());
256
257 aChild.IncrementCslTxAttempts();
258 LogInfo("CSL tx to child %04x failed, attempt %d/%d", aChild.GetRloc16(), aChild.GetCslTxAttempts(),
259 kMaxCslTriggeredTxAttempts);
260
261 if (aChild.GetCslTxAttempts() >= kMaxCslTriggeredTxAttempts)
262 {
263 // CSL transmission attempts reach max, consider child out of sync
264 aChild.SetCslSynchronized(false);
265 aChild.ResetCslTxAttempts();
266 }
267
268 OT_FALL_THROUGH;
269
270 case kErrorChannelAccessFailure:
271 case kErrorAbort:
272
273 // Even if CSL tx attempts count reaches max, the message won't be
274 // dropped until indirect tx attempts count reaches max. So here it
275 // would set sequence number and schedule next CSL tx.
276
277 if (!aFrame.IsEmpty())
278 {
279 aChild.SetIndirectDataSequenceNumber(aFrame.GetSequence());
280
281 if (aFrame.GetSecurityEnabled() && aFrame.IsHeaderUpdated())
282 {
283 uint32_t frameCounter;
284 uint8_t keyId;
285
286 IgnoreError(aFrame.GetFrameCounter(frameCounter));
287 aChild.SetIndirectFrameCounter(frameCounter);
288
289 IgnoreError(aFrame.GetKeyId(keyId));
290 aChild.SetIndirectKeyId(keyId);
291 }
292 }
293
294 RescheduleCslTx();
295 ExitNow();
296
297 default:
298 OT_ASSERT(false);
299 OT_UNREACHABLE_CODE(break);
300 }
301
302 mCallbacks.HandleSentFrameToChild(aFrame, mFrameContext, aError, aChild);
303
304 exit:
305 return;
306 }
307
308 } // namespace ot
309
310 #endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
311