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