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