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