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