1 /*
2  *  Copyright (c) 2018, 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 /**
30  * @file
31  *   This file implements Channel Manager.
32  *
33  */
34 
35 #include "channel_manager.hpp"
36 
37 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE
38 #if (OPENTHREAD_FTD || OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)
39 
40 #include "common/code_utils.hpp"
41 #include "common/locator_getters.hpp"
42 #include "common/log.hpp"
43 #include "common/random.hpp"
44 #include "common/string.hpp"
45 #include "instance/instance.hpp"
46 #include "meshcop/dataset_updater.hpp"
47 #include "radio/radio.hpp"
48 
49 namespace ot {
50 namespace Utils {
51 
52 RegisterLogModule("ChannelManager");
53 
ChannelManager(Instance & aInstance)54 ChannelManager::ChannelManager(Instance &aInstance)
55     : InstanceLocator(aInstance)
56     , mSupportedChannelMask(0)
57     , mFavoredChannelMask(0)
58 #if OPENTHREAD_FTD
59     , mDelay(kMinimumDelay)
60 #endif
61     , mChannel(0)
62     , mChannelSelected(0)
63     , mState(kStateIdle)
64     , mTimer(aInstance)
65     , mAutoSelectInterval(kDefaultAutoSelectInterval)
66 #if OPENTHREAD_FTD
67     , mAutoSelectEnabled(false)
68 #endif
69 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE
70     , mAutoSelectCslEnabled(false)
71 #endif
72     , mCcaFailureRateThreshold(kCcaFailureRateThreshold)
73 {
74 }
75 
RequestChannelChange(uint8_t aChannel)76 void ChannelManager::RequestChannelChange(uint8_t aChannel)
77 {
78 #if OPENTHREAD_FTD
79     if (Get<Mle::Mle>().IsFullThreadDevice() && Get<Mle::Mle>().IsRxOnWhenIdle() && mAutoSelectEnabled)
80     {
81         RequestNetworkChannelChange(aChannel);
82     }
83 #endif
84 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE
85     if (mAutoSelectCslEnabled)
86     {
87         ChangeCslChannel(aChannel);
88     }
89 #endif
90 }
91 
92 #if OPENTHREAD_FTD
RequestNetworkChannelChange(uint8_t aChannel)93 void ChannelManager::RequestNetworkChannelChange(uint8_t aChannel)
94 {
95     // Check requested channel != current channel
96     if (aChannel == Get<Mac::Mac>().GetPanChannel())
97     {
98         LogInfo("Already operating on the requested channel %d", aChannel);
99         ExitNow();
100     }
101 
102     LogInfo("Request to change to channel %d with delay %d sec", aChannel, mDelay);
103     if (mState == kStateChangeInProgress)
104     {
105         VerifyOrExit(mChannel != aChannel);
106     }
107 
108     mState   = kStateChangeRequested;
109     mChannel = aChannel;
110 
111     mTimer.Start(1 + Random::NonCrypto::GetUint32InRange(0, kRequestStartJitterInterval));
112 
113     Get<Notifier>().Signal(kEventChannelManagerNewChannelChanged);
114 
115 exit:
116     return;
117 }
118 #endif
119 
120 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE
ChangeCslChannel(uint8_t aChannel)121 void ChannelManager::ChangeCslChannel(uint8_t aChannel)
122 {
123     if (!(!Get<Mle::Mle>().IsRxOnWhenIdle() && Get<Mac::Mac>().IsCslEnabled()))
124     {
125         // cannot select or use other channel
126         ExitNow();
127     }
128 
129     if (aChannel == Get<Mac::Mac>().GetCslChannel())
130     {
131         LogInfo("Already operating on the requested channel %d", aChannel);
132         ExitNow();
133     }
134 
135     VerifyOrExit(Radio::IsCslChannelValid(aChannel));
136 
137     LogInfo("Change to Csl channel %d now.", aChannel);
138 
139     mChannel = aChannel;
140     Get<Mac::Mac>().SetCslChannel(aChannel);
141 
142 exit:
143     return;
144 }
145 #endif // OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE
146 
147 #if OPENTHREAD_FTD
SetDelay(uint16_t aDelay)148 Error ChannelManager::SetDelay(uint16_t aDelay)
149 {
150     Error error = kErrorNone;
151 
152     VerifyOrExit(aDelay >= kMinimumDelay, error = kErrorInvalidArgs);
153     mDelay = aDelay;
154 
155 exit:
156     return error;
157 }
158 
StartDatasetUpdate(void)159 void ChannelManager::StartDatasetUpdate(void)
160 {
161     MeshCoP::Dataset::Info dataset;
162 
163     dataset.Clear();
164     dataset.Set<MeshCoP::Dataset::kChannel>(mChannel);
165     dataset.Set<MeshCoP::Dataset::kDelay>(Time::SecToMsec(mDelay));
166 
167     switch (Get<MeshCoP::DatasetUpdater>().RequestUpdate(dataset, HandleDatasetUpdateDone, this))
168     {
169     case kErrorNone:
170         mState = kStateChangeInProgress;
171         // Wait for the `HandleDatasetUpdateDone()` callback.
172         break;
173 
174     case kErrorBusy:
175     case kErrorNoBufs:
176         mTimer.Start(kPendingDatasetTxRetryInterval);
177         break;
178 
179     case kErrorInvalidState:
180         LogInfo("Request to change to channel %d failed. Device is disabled", mChannel);
181 
182         OT_FALL_THROUGH;
183 
184     default:
185         mState = kStateIdle;
186         StartAutoSelectTimer();
187         break;
188     }
189 }
190 
HandleDatasetUpdateDone(Error aError,void * aContext)191 void ChannelManager::HandleDatasetUpdateDone(Error aError, void *aContext)
192 {
193     static_cast<ChannelManager *>(aContext)->HandleDatasetUpdateDone(aError);
194 }
195 
HandleDatasetUpdateDone(Error aError)196 void ChannelManager::HandleDatasetUpdateDone(Error aError)
197 {
198     if (aError == kErrorNone)
199     {
200         LogInfo("Channel changed to %d", mChannel);
201     }
202     else
203     {
204         LogInfo("Canceling channel change to %d%s", mChannel,
205                 (aError == kErrorAlready) ? " since current ActiveDataset is more recent" : "");
206     }
207 
208     mState = kStateIdle;
209     StartAutoSelectTimer();
210 }
211 #endif // OPENTHREAD_FTD
212 
HandleTimer(void)213 void ChannelManager::HandleTimer(void)
214 {
215     switch (mState)
216     {
217     case kStateIdle:
218 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
219         LogInfo("Auto-triggered channel select");
220         IgnoreError(RequestAutoChannelSelect(false));
221 #endif
222         StartAutoSelectTimer();
223         break;
224 
225     case kStateChangeRequested:
226 #if OPENTHREAD_FTD
227         StartDatasetUpdate();
228 #endif
229         break;
230 
231     case kStateChangeInProgress:
232         break;
233     }
234 }
235 
236 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
237 
FindBetterChannel(uint8_t & aNewChannel,uint16_t & aOccupancy)238 Error ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy)
239 {
240     Error            error = kErrorNone;
241     Mac::ChannelMask favoredAndSupported;
242     Mac::ChannelMask favoredBest;
243     Mac::ChannelMask supportedBest;
244     uint16_t         favoredOccupancy;
245     uint16_t         supportedOccupancy;
246 
247     if (Get<ChannelMonitor>().GetSampleCount() <= kMinChannelMonitorSampleCount)
248     {
249         LogInfo("Too few samples (%lu <= %lu) to select channel", ToUlong(Get<ChannelMonitor>().GetSampleCount()),
250                 ToUlong(kMinChannelMonitorSampleCount));
251         ExitNow(error = kErrorInvalidState);
252     }
253 
254     favoredAndSupported = mFavoredChannelMask;
255     favoredAndSupported.Intersect(mSupportedChannelMask);
256 
257     favoredBest   = Get<ChannelMonitor>().FindBestChannels(favoredAndSupported, favoredOccupancy);
258     supportedBest = Get<ChannelMonitor>().FindBestChannels(mSupportedChannelMask, supportedOccupancy);
259 
260     LogInfo("Best favored %s, occupancy 0x%04x", favoredBest.ToString().AsCString(), favoredOccupancy);
261     LogInfo("Best overall %s, occupancy 0x%04x", supportedBest.ToString().AsCString(), supportedOccupancy);
262 
263     // Prefer favored channels unless there is no favored channel,
264     // or the occupancy rate of the best favored channel is worse
265     // than the best overall by at least `kThresholdToSkipFavored`.
266 
267     if (favoredBest.IsEmpty() || ((favoredOccupancy >= kThresholdToSkipFavored) &&
268                                   (supportedOccupancy < favoredOccupancy - kThresholdToSkipFavored)))
269     {
270         if (!favoredBest.IsEmpty())
271         {
272             LogInfo("Preferring an unfavored channel due to high occupancy rate diff");
273         }
274 
275         favoredBest      = supportedBest;
276         favoredOccupancy = supportedOccupancy;
277     }
278 
279     VerifyOrExit(!favoredBest.IsEmpty(), error = kErrorNotFound);
280 
281     aNewChannel = favoredBest.ChooseRandomChannel();
282     aOccupancy  = favoredOccupancy;
283 
284 exit:
285     return error;
286 }
287 
ShouldAttemptChannelChange(void)288 bool ChannelManager::ShouldAttemptChannelChange(void)
289 {
290     uint16_t ccaFailureRate = Get<Mac::Mac>().GetCcaFailureRate();
291     bool     shouldAttempt  = (ccaFailureRate >= mCcaFailureRateThreshold);
292 
293     LogInfo("CCA-err-rate: 0x%04x %s 0x%04x, selecting channel: %s", ccaFailureRate, shouldAttempt ? ">=" : "<",
294             mCcaFailureRateThreshold, ToYesNo(shouldAttempt));
295 
296     return shouldAttempt;
297 }
298 
299 #if OPENTHREAD_FTD
RequestNetworkChannelSelect(bool aSkipQualityCheck)300 Error ChannelManager::RequestNetworkChannelSelect(bool aSkipQualityCheck)
301 {
302     Error error = kErrorNone;
303 
304     SuccessOrExit(error = RequestChannelSelect(aSkipQualityCheck));
305     RequestNetworkChannelChange(mChannelSelected);
306 
307 exit:
308     if ((error == kErrorAbort) || (error == kErrorAlready))
309     {
310         // ignore aborted channel change
311         error = kErrorNone;
312     }
313     return error;
314 }
315 #endif
316 
317 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE
RequestCslChannelSelect(bool aSkipQualityCheck)318 Error ChannelManager::RequestCslChannelSelect(bool aSkipQualityCheck)
319 {
320     Error error = kErrorNone;
321 
322     SuccessOrExit(error = RequestChannelSelect(aSkipQualityCheck));
323     ChangeCslChannel(mChannelSelected);
324 
325 exit:
326     if ((error == kErrorAbort) || (error == kErrorAlready))
327     {
328         // ignore aborted channel change
329         error = kErrorNone;
330     }
331     return error;
332 }
333 #endif
334 
RequestAutoChannelSelect(bool aSkipQualityCheck)335 Error ChannelManager::RequestAutoChannelSelect(bool aSkipQualityCheck)
336 {
337     Error error = kErrorNone;
338 
339     SuccessOrExit(error = RequestChannelSelect(aSkipQualityCheck));
340     RequestChannelChange(mChannelSelected);
341 
342 exit:
343     return error;
344 }
345 
RequestChannelSelect(bool aSkipQualityCheck)346 Error ChannelManager::RequestChannelSelect(bool aSkipQualityCheck)
347 {
348     Error    error = kErrorNone;
349     uint8_t  curChannel, newChannel;
350     uint16_t curOccupancy, newOccupancy;
351 
352     LogInfo("Request to select channel (skip quality check: %s)", ToYesNo(aSkipQualityCheck));
353 
354     VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), error = kErrorInvalidState);
355 
356     VerifyOrExit(aSkipQualityCheck || ShouldAttemptChannelChange(), error = kErrorAbort);
357 
358     SuccessOrExit(error = FindBetterChannel(newChannel, newOccupancy));
359 
360 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE
361     if (Get<Mac::Mac>().IsCslEnabled() && (Get<Mac::Mac>().GetCslChannel() != 0))
362     {
363         curChannel = Get<Mac::Mac>().GetCslChannel();
364     }
365     else
366 #endif
367     {
368         curChannel = Get<Mac::Mac>().GetPanChannel();
369     }
370 
371     curOccupancy = Get<ChannelMonitor>().GetChannelOccupancy(curChannel);
372 
373     if (newChannel == curChannel)
374     {
375         LogInfo("Already on best possible channel %d", curChannel);
376         ExitNow(error = kErrorAlready);
377     }
378 
379     LogInfo("Cur channel %d, occupancy 0x%04x - Best channel %d, occupancy 0x%04x", curChannel, curOccupancy,
380             newChannel, newOccupancy);
381 
382     // Switch only if new channel's occupancy rate is better than current
383     // channel's occupancy rate by threshold `kThresholdToChangeChannel`.
384 
385     if ((newOccupancy >= curOccupancy) ||
386         (static_cast<uint16_t>(curOccupancy - newOccupancy) < kThresholdToChangeChannel))
387     {
388         LogInfo("Occupancy rate diff too small to change channel");
389         ExitNow(error = kErrorAbort);
390     }
391 
392     mChannelSelected = newChannel;
393 
394 exit:
395     LogWarnOnError(error, "select better channel");
396     return error;
397 }
398 #endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
399 
StartAutoSelectTimer(void)400 void ChannelManager::StartAutoSelectTimer(void)
401 {
402     VerifyOrExit(mState == kStateIdle);
403 
404 #if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)
405     if (mAutoSelectEnabled || mAutoSelectCslEnabled)
406 #elif OPENTHREAD_FTD
407     if (mAutoSelectEnabled)
408 #elif OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE
409     if (mAutoSelectCslEnabled)
410 #endif
411     {
412         mTimer.Start(Time::SecToMsec(mAutoSelectInterval));
413     }
414     else
415     {
416         mTimer.Stop();
417     }
418 
419 exit:
420     return;
421 }
422 
423 #if OPENTHREAD_FTD
SetAutoNetworkChannelSelectionEnabled(bool aEnabled)424 void ChannelManager::SetAutoNetworkChannelSelectionEnabled(bool aEnabled)
425 {
426 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
427     if (aEnabled != mAutoSelectEnabled)
428     {
429         mAutoSelectEnabled = aEnabled;
430         IgnoreError(RequestNetworkChannelSelect(false));
431         StartAutoSelectTimer();
432     }
433 #endif
434 }
435 #endif
436 
437 #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE
SetAutoCslChannelSelectionEnabled(bool aEnabled)438 void ChannelManager::SetAutoCslChannelSelectionEnabled(bool aEnabled)
439 {
440     if (aEnabled != mAutoSelectCslEnabled)
441     {
442         mAutoSelectCslEnabled = aEnabled;
443         IgnoreError(RequestAutoChannelSelect(false));
444         StartAutoSelectTimer();
445     }
446 }
447 #endif
448 
SetAutoChannelSelectionInterval(uint32_t aInterval)449 Error ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval)
450 {
451     Error    error        = kErrorNone;
452     uint32_t prevInterval = mAutoSelectInterval;
453 
454     VerifyOrExit((aInterval != 0) && (aInterval <= Time::MsecToSec(Timer::kMaxDelay)), error = kErrorInvalidArgs);
455 
456     mAutoSelectInterval = aInterval;
457 
458 #if (OPENTHREAD_FTD && OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)
459     if (mAutoSelectEnabled || mAutoSelectCslEnabled)
460 #elif OPENTHREAD_FTD
461     if (mAutoSelectEnabled)
462 #elif OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE
463     if (mAutoSelectCslEnabled)
464 #endif
465     {
466         if ((mState == kStateIdle) && mTimer.IsRunning() && (prevInterval != aInterval))
467         {
468             mTimer.StartAt(mTimer.GetFireTime() - Time::SecToMsec(prevInterval), Time::SecToMsec(aInterval));
469         }
470     }
471 
472 exit:
473     return error;
474 }
475 
SetSupportedChannels(uint32_t aChannelMask)476 void ChannelManager::SetSupportedChannels(uint32_t aChannelMask)
477 {
478     mSupportedChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());
479 
480     LogInfo("Supported channels: %s", mSupportedChannelMask.ToString().AsCString());
481 }
482 
SetFavoredChannels(uint32_t aChannelMask)483 void ChannelManager::SetFavoredChannels(uint32_t aChannelMask)
484 {
485     mFavoredChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());
486 
487     LogInfo("Favored channels: %s", mFavoredChannelMask.ToString().AsCString());
488 }
489 
SetCcaFailureRateThreshold(uint16_t aThreshold)490 void ChannelManager::SetCcaFailureRateThreshold(uint16_t aThreshold)
491 {
492     mCcaFailureRateThreshold = aThreshold;
493 
494     LogInfo("CCA threshold: 0x%04x", mCcaFailureRateThreshold);
495 }
496 
497 } // namespace Utils
498 } // namespace ot
499 
500 #endif // #if (OPENTHREAD_FTD || OPENTHREAD_CONFIG_CHANNEL_MANAGER_CSL_CHANNEL_SELECT_ENABLE)
501 #endif // #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE
502