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