• 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_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
32 
33 #include "instance/instance.hpp"
34 
35 namespace ot {
36 
37 RegisterLogModule("CslTxScheduler");
38 
CslTxScheduler(Instance & aInstance)39 CslTxScheduler::CslTxScheduler(Instance &aInstance)
40     : InstanceLocator(aInstance)
41     , mCslTxNeighbor(nullptr)
42     , mCslTxMessage(nullptr)
43     , mFrameContext()
44 {
45     UpdateFrameRequestAhead();
46 }
47 
UpdateFrameRequestAhead(void)48 void CslTxScheduler::UpdateFrameRequestAhead(void)
49 {
50     // Longest frame on bus is 127 bytes with some metadata, we use
51     // 150 bytes for bus Tx time estimation
52     static constexpr uint16_t kMaxFrameSize = 150;
53 
54     mCslFrameRequestAheadUs = Mac::kCslRequestAhead + Get<Mac::Mac>().CalculateRadioBusTransferTime(kMaxFrameSize);
55 
56     LogInfo("Set frame request ahead: %lu usec", ToUlong(mCslFrameRequestAheadUs));
57 }
58 
Update(void)59 void CslTxScheduler::Update(void)
60 {
61     if (mCslTxMessage == nullptr)
62     {
63         RescheduleCslTx();
64     }
65     else if ((mCslTxNeighbor != nullptr) && (mCslTxNeighbor->GetIndirectMessage() != mCslTxMessage))
66     {
67         // `Mac` has already started the CSL tx, so wait for tx done callback
68         // to call `RescheduleCslTx`
69         mCslTxNeighbor->ResetCslTxAttempts();
70         mCslTxNeighbor                   = nullptr;
71         mFrameContext.mMessageNextOffset = 0;
72     }
73 }
74 
Clear(void)75 void CslTxScheduler::Clear(void)
76 {
77 #if OPENTHREAD_FTD
78     for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
79     {
80         child.ResetCslTxAttempts();
81         child.SetCslSynchronized(false);
82         child.SetCslChannel(0);
83         child.SetCslTimeout(0);
84         child.SetCslPeriod(0);
85         child.SetCslPhase(0);
86         child.SetCslLastHeard(TimeMilli(0));
87     }
88 #endif
89 
90     mFrameContext.mMessageNextOffset = 0;
91     mCslTxNeighbor                   = nullptr;
92     mCslTxMessage                    = nullptr;
93 }
94 
95 /**
96  * Always finds the most recent CSL tx among all children,
97  * and requests `Mac` to do CSL tx at specific time. It shouldn't be called
98  * when `Mac` is already starting to do the CSL tx (indicated by `mCslTxMessage`).
99  */
RescheduleCslTx(void)100 void CslTxScheduler::RescheduleCslTx(void)
101 {
102     uint32_t     minDelayTime = Time::kMaxDuration;
103     CslNeighbor *bestNeighbor = nullptr;
104 
105 #if OPENTHREAD_FTD
106     for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid))
107     {
108         uint32_t delay;
109         uint32_t cslTxDelay;
110 
111         if (!child.IsCslSynchronized() || child.GetIndirectMessageCount() == 0)
112         {
113             continue;
114         }
115 
116         delay = GetNextCslTransmissionDelay(child, cslTxDelay, mCslFrameRequestAheadUs);
117 
118         if (delay < minDelayTime)
119         {
120             minDelayTime = delay;
121             bestNeighbor = &child;
122         }
123     }
124 #endif
125 
126     if (bestNeighbor != nullptr)
127     {
128         Get<Mac::Mac>().RequestCslFrameTransmission(minDelayTime / 1000UL);
129     }
130 
131     mCslTxNeighbor = bestNeighbor;
132 }
133 
GetNextCslTransmissionDelay(const CslNeighbor & aCslNeighbor,uint32_t & aDelayFromLastRx,uint32_t aAheadUs) const134 uint32_t CslTxScheduler::GetNextCslTransmissionDelay(const CslNeighbor &aCslNeighbor,
135                                                      uint32_t          &aDelayFromLastRx,
136                                                      uint32_t           aAheadUs) const
137 {
138     uint64_t radioNow   = Get<Radio>().GetNow();
139     uint32_t periodInUs = aCslNeighbor.GetCslPeriod() * kUsPerTenSymbols;
140 
141     /* see CslTxScheduler::NeighborInfo::mCslPhase */
142     uint64_t firstTxWindow = aCslNeighbor.GetLastRxTimestamp() + aCslNeighbor.GetCslPhase() * kUsPerTenSymbols;
143     uint64_t nextTxWindow  = radioNow - (radioNow % periodInUs) + (firstTxWindow % periodInUs);
144 
145     while (nextTxWindow < radioNow + aAheadUs)
146     {
147         nextTxWindow += periodInUs;
148     }
149 
150     aDelayFromLastRx = static_cast<uint32_t>(nextTxWindow - aCslNeighbor.GetLastRxTimestamp());
151 
152     return static_cast<uint32_t>(nextTxWindow - radioNow - aAheadUs);
153 }
154 
155 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
156 
HandleFrameRequest(Mac::TxFrames & aTxFrames)157 Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &aTxFrames)
158 {
159     Mac::TxFrame *frame = nullptr;
160     uint32_t      txDelay;
161     uint32_t      delay;
162 
163     VerifyOrExit(mCslTxNeighbor != nullptr);
164     VerifyOrExit(mCslTxNeighbor->IsCslSynchronized());
165 
166 #if OPENTHREAD_CONFIG_MULTI_RADIO
167     frame = &aTxFrames.GetTxFrame(Mac::kRadioTypeIeee802154);
168 #else
169     frame = &aTxFrames.GetTxFrame();
170 #endif
171 
172     VerifyOrExit(Get<IndirectSender>().PrepareFrameForCslNeighbor(*frame, mFrameContext, *mCslTxNeighbor) == kErrorNone,
173                  frame = nullptr);
174     mCslTxMessage = mCslTxNeighbor->GetIndirectMessage();
175     VerifyOrExit(mCslTxMessage != nullptr, frame = nullptr);
176 
177     if (mCslTxNeighbor->GetIndirectTxAttempts() > 0 || mCslTxNeighbor->GetCslTxAttempts() > 0)
178     {
179         // For a re-transmission of an indirect frame to a sleepy
180         // child, we ensure to use the same frame counter, key id, and
181         // data sequence number as the previous attempt.
182 
183         frame->SetIsARetransmission(true);
184         frame->SetSequence(mCslTxNeighbor->GetIndirectDataSequenceNumber());
185 
186         if (frame->GetSecurityEnabled())
187         {
188             frame->SetFrameCounter(mCslTxNeighbor->GetIndirectFrameCounter());
189             frame->SetKeyId(mCslTxNeighbor->GetIndirectKeyId());
190         }
191     }
192     else
193     {
194         frame->SetIsARetransmission(false);
195     }
196 
197     frame->SetChannel(mCslTxNeighbor->GetCslChannel() == 0 ? Get<Mac::Mac>().GetPanChannel()
198                                                            : mCslTxNeighbor->GetCslChannel());
199 
200     if (frame->GetChannel() != Get<Mac::Mac>().GetPanChannel())
201     {
202         frame->SetRxChannelAfterTxDone(Get<Mac::Mac>().GetPanChannel());
203     }
204 
205     delay = GetNextCslTransmissionDelay(*mCslTxNeighbor, txDelay, /* aAheadUs */ 0);
206 
207     // We make sure that delay is less than `mCslFrameRequestAheadUs`
208     // plus some guard time. Note that we used `mCslFrameRequestAheadUs`
209     // in `RescheduleCslTx()` when determining the next CSL delay to
210     // schedule CSL tx with `Mac` but here we calculate the delay with
211     // zero `aAheadUs`. All the timings are in usec but when passing
212     // delay to `Mac` we divide by `1000` (to convert to msec) which
213     // can round the value down and cause `Mac` to start operation a
214     // bit (some usec) earlier. This is covered by adding the guard
215     // time `kFramePreparationGuardInterval`.
216     //
217     // In general this check handles the case where `Mac` is busy with
218     // other operations and therefore late to start the CSL tx operation
219     // and by the time `HandleFrameRequest()` is invoked, we miss the
220     // current CSL window and move to the next window.
221 
222     VerifyOrExit(delay <= mCslFrameRequestAheadUs + kFramePreparationGuardInterval, frame = nullptr);
223 
224     frame->SetTxDelay(txDelay);
225     frame->SetTxDelayBaseTime(
226         static_cast<uint32_t>(mCslTxNeighbor->GetLastRxTimestamp())); // Only LSB part of the time is required.
227     frame->SetCsmaCaEnabled(true);
228 
229 exit:
230     return frame;
231 }
232 
233 #else // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
234 
HandleFrameRequest(Mac::TxFrames &)235 Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &) { return nullptr; }
236 
237 #endif // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
238 
HandleSentFrame(const Mac::TxFrame & aFrame,Error aError)239 void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError)
240 {
241     CslNeighbor *neighbor = mCslTxNeighbor;
242 
243     mCslTxMessage = nullptr;
244 
245     VerifyOrExit(neighbor != nullptr);
246 
247     mCslTxNeighbor = nullptr;
248 
249     HandleSentFrame(aFrame, aError, *neighbor);
250 
251 exit:
252     RescheduleCslTx();
253 }
254 
HandleSentFrame(const Mac::TxFrame & aFrame,Error aError,CslNeighbor & aCslNeighbor)255 void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, CslNeighbor &aCslNeighbor)
256 {
257     switch (aError)
258     {
259     case kErrorNone:
260         aCslNeighbor.ResetCslTxAttempts();
261         aCslNeighbor.ResetIndirectTxAttempts();
262         break;
263 
264     case kErrorNoAck:
265         OT_ASSERT(!aFrame.GetSecurityEnabled() || aFrame.IsHeaderUpdated());
266 
267         aCslNeighbor.IncrementCslTxAttempts();
268         LogInfo("CSL tx to %04x failed, attempt %d/%d", aCslNeighbor.GetRloc16(), aCslNeighbor.GetCslTxAttempts(),
269                 kMaxCslTriggeredTxAttempts);
270 
271         if (aCslNeighbor.GetCslTxAttempts() >= kMaxCslTriggeredTxAttempts)
272         {
273             // CSL transmission attempts reach max, consider child out of sync
274             aCslNeighbor.SetCslSynchronized(false);
275             aCslNeighbor.ResetCslTxAttempts();
276         }
277 
278         OT_FALL_THROUGH;
279 
280     case kErrorChannelAccessFailure:
281     case kErrorAbort:
282 
283         // Even if CSL tx attempts count reaches max, the message won't be
284         // dropped until indirect tx attempts count reaches max. So here it
285         // would set sequence number and schedule next CSL tx.
286 
287         if (!aFrame.IsEmpty())
288         {
289             aCslNeighbor.SetIndirectDataSequenceNumber(aFrame.GetSequence());
290 
291             if (aFrame.GetSecurityEnabled() && aFrame.IsHeaderUpdated())
292             {
293                 uint32_t frameCounter;
294                 uint8_t  keyId;
295 
296                 IgnoreError(aFrame.GetFrameCounter(frameCounter));
297                 aCslNeighbor.SetIndirectFrameCounter(frameCounter);
298 
299                 IgnoreError(aFrame.GetKeyId(keyId));
300                 aCslNeighbor.SetIndirectKeyId(keyId);
301             }
302         }
303 
304         ExitNow();
305 
306     default:
307         OT_ASSERT(false);
308         OT_UNREACHABLE_CODE(break);
309     }
310 
311     Get<IndirectSender>().HandleSentFrameToCslNeighbor(aFrame, mFrameContext, aError, aCslNeighbor);
312 
313 exit:
314     return;
315 }
316 
317 } // namespace ot
318 
319 #endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
320