• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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