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 && OPENTHREAD_FTD
38
39 #include "common/code_utils.hpp"
40 #include "common/instance.hpp"
41 #include "common/locator_getters.hpp"
42 #include "common/log.hpp"
43 #include "common/random.hpp"
44 #include "common/string.hpp"
45 #include "meshcop/dataset_updater.hpp"
46 #include "radio/radio.hpp"
47
48 namespace ot {
49 namespace Utils {
50
51 RegisterLogModule("ChannelManager");
52
ChannelManager(Instance & aInstance)53 ChannelManager::ChannelManager(Instance &aInstance)
54 : InstanceLocator(aInstance)
55 , mSupportedChannelMask(0)
56 , mFavoredChannelMask(0)
57 , mDelay(kMinimumDelay)
58 , mChannel(0)
59 , mState(kStateIdle)
60 , mTimer(aInstance)
61 , mAutoSelectInterval(kDefaultAutoSelectInterval)
62 , mAutoSelectEnabled(false)
63 , mCcaFailureRateThreshold(kCcaFailureRateThreshold)
64 {
65 }
66
RequestChannelChange(uint8_t aChannel)67 void ChannelManager::RequestChannelChange(uint8_t aChannel)
68 {
69 LogInfo("Request to change to channel %d with delay %d sec", aChannel, mDelay);
70
71 if (aChannel == Get<Mac::Mac>().GetPanChannel())
72 {
73 LogInfo("Already operating on the requested channel %d", aChannel);
74 ExitNow();
75 }
76
77 if (mState == kStateChangeInProgress)
78 {
79 VerifyOrExit(mChannel != aChannel);
80 }
81
82 mState = kStateChangeRequested;
83 mChannel = aChannel;
84
85 mTimer.Start(1 + Random::NonCrypto::GetUint32InRange(0, kRequestStartJitterInterval));
86
87 Get<Notifier>().Signal(kEventChannelManagerNewChannelChanged);
88
89 exit:
90 return;
91 }
92
SetDelay(uint16_t aDelay)93 Error ChannelManager::SetDelay(uint16_t aDelay)
94 {
95 Error error = kErrorNone;
96
97 VerifyOrExit(aDelay >= kMinimumDelay, error = kErrorInvalidArgs);
98 mDelay = aDelay;
99
100 exit:
101 return error;
102 }
103
StartDatasetUpdate(void)104 void ChannelManager::StartDatasetUpdate(void)
105 {
106 MeshCoP::Dataset::Info dataset;
107
108 dataset.Clear();
109 dataset.SetChannel(mChannel);
110 dataset.SetDelay(Time::SecToMsec(mDelay));
111
112 switch (Get<MeshCoP::DatasetUpdater>().RequestUpdate(dataset, HandleDatasetUpdateDone, this))
113 {
114 case kErrorNone:
115 mState = kStateChangeInProgress;
116 // Wait for the `HandleDatasetUpdateDone()` callback.
117 break;
118
119 case kErrorBusy:
120 case kErrorNoBufs:
121 mTimer.Start(kPendingDatasetTxRetryInterval);
122 break;
123
124 case kErrorInvalidState:
125 LogInfo("Request to change to channel %d failed. Device is disabled", mChannel);
126
127 OT_FALL_THROUGH;
128
129 default:
130 mState = kStateIdle;
131 StartAutoSelectTimer();
132 break;
133 }
134 }
135
HandleDatasetUpdateDone(Error aError,void * aContext)136 void ChannelManager::HandleDatasetUpdateDone(Error aError, void *aContext)
137 {
138 static_cast<ChannelManager *>(aContext)->HandleDatasetUpdateDone(aError);
139 }
140
HandleDatasetUpdateDone(Error aError)141 void ChannelManager::HandleDatasetUpdateDone(Error aError)
142 {
143 if (aError == kErrorNone)
144 {
145 LogInfo("Channel changed to %d", mChannel);
146 }
147 else
148 {
149 LogInfo("Canceling channel change to %d%s", mChannel,
150 (aError == kErrorAlready) ? " since current ActiveDataset is more recent" : "");
151 }
152
153 mState = kStateIdle;
154 StartAutoSelectTimer();
155 }
156
HandleTimer(void)157 void ChannelManager::HandleTimer(void)
158 {
159 switch (mState)
160 {
161 case kStateIdle:
162 LogInfo("Auto-triggered channel select");
163 IgnoreError(RequestChannelSelect(false));
164 StartAutoSelectTimer();
165 break;
166
167 case kStateChangeRequested:
168 StartDatasetUpdate();
169 break;
170
171 case kStateChangeInProgress:
172 break;
173 }
174 }
175
176 #if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
177
FindBetterChannel(uint8_t & aNewChannel,uint16_t & aOccupancy)178 Error ChannelManager::FindBetterChannel(uint8_t &aNewChannel, uint16_t &aOccupancy)
179 {
180 Error error = kErrorNone;
181 Mac::ChannelMask favoredAndSupported;
182 Mac::ChannelMask favoredBest;
183 Mac::ChannelMask supportedBest;
184 uint16_t favoredOccupancy;
185 uint16_t supportedOccupancy;
186
187 if (Get<ChannelMonitor>().GetSampleCount() <= kMinChannelMonitorSampleCount)
188 {
189 LogInfo("Too few samples (%lu <= %lu) to select channel", ToUlong(Get<ChannelMonitor>().GetSampleCount()),
190 ToUlong(kMinChannelMonitorSampleCount));
191 ExitNow(error = kErrorInvalidState);
192 }
193
194 favoredAndSupported = mFavoredChannelMask;
195 favoredAndSupported.Intersect(mSupportedChannelMask);
196
197 favoredBest = Get<ChannelMonitor>().FindBestChannels(favoredAndSupported, favoredOccupancy);
198 supportedBest = Get<ChannelMonitor>().FindBestChannels(mSupportedChannelMask, supportedOccupancy);
199
200 LogInfo("Best favored %s, occupancy 0x%04x", favoredBest.ToString().AsCString(), favoredOccupancy);
201 LogInfo("Best overall %s, occupancy 0x%04x", supportedBest.ToString().AsCString(), supportedOccupancy);
202
203 // Prefer favored channels unless there is no favored channel,
204 // or the occupancy rate of the best favored channel is worse
205 // than the best overall by at least `kThresholdToSkipFavored`.
206
207 if (favoredBest.IsEmpty() || ((favoredOccupancy >= kThresholdToSkipFavored) &&
208 (supportedOccupancy < favoredOccupancy - kThresholdToSkipFavored)))
209 {
210 if (!favoredBest.IsEmpty())
211 {
212 LogInfo("Preferring an unfavored channel due to high occupancy rate diff");
213 }
214
215 favoredBest = supportedBest;
216 favoredOccupancy = supportedOccupancy;
217 }
218
219 VerifyOrExit(!favoredBest.IsEmpty(), error = kErrorNotFound);
220
221 aNewChannel = favoredBest.ChooseRandomChannel();
222 aOccupancy = favoredOccupancy;
223
224 exit:
225 return error;
226 }
227
ShouldAttemptChannelChange(void)228 bool ChannelManager::ShouldAttemptChannelChange(void)
229 {
230 uint16_t ccaFailureRate = Get<Mac::Mac>().GetCcaFailureRate();
231 bool shouldAttempt = (ccaFailureRate >= mCcaFailureRateThreshold);
232
233 LogInfo("CCA-err-rate: 0x%04x %s 0x%04x, selecting channel: %s", ccaFailureRate, shouldAttempt ? ">=" : "<",
234 mCcaFailureRateThreshold, ToYesNo(shouldAttempt));
235
236 return shouldAttempt;
237 }
238
RequestChannelSelect(bool aSkipQualityCheck)239 Error ChannelManager::RequestChannelSelect(bool aSkipQualityCheck)
240 {
241 Error error = kErrorNone;
242 uint8_t curChannel, newChannel;
243 uint16_t curOccupancy, newOccupancy;
244
245 LogInfo("Request to select channel (skip quality check: %s)", ToYesNo(aSkipQualityCheck));
246
247 VerifyOrExit(!Get<Mle::Mle>().IsDisabled(), error = kErrorInvalidState);
248
249 VerifyOrExit(aSkipQualityCheck || ShouldAttemptChannelChange());
250
251 SuccessOrExit(error = FindBetterChannel(newChannel, newOccupancy));
252
253 curChannel = Get<Mac::Mac>().GetPanChannel();
254 curOccupancy = Get<ChannelMonitor>().GetChannelOccupancy(curChannel);
255
256 if (newChannel == curChannel)
257 {
258 LogInfo("Already on best possible channel %d", curChannel);
259 ExitNow();
260 }
261
262 LogInfo("Cur channel %d, occupancy 0x%04x - Best channel %d, occupancy 0x%04x", curChannel, curOccupancy,
263 newChannel, newOccupancy);
264
265 // Switch only if new channel's occupancy rate is better than current
266 // channel's occupancy rate by threshold `kThresholdToChangeChannel`.
267
268 if ((newOccupancy >= curOccupancy) ||
269 (static_cast<uint16_t>(curOccupancy - newOccupancy) < kThresholdToChangeChannel))
270 {
271 LogInfo("Occupancy rate diff too small to change channel");
272 ExitNow();
273 }
274
275 RequestChannelChange(newChannel);
276
277 exit:
278
279 if (error != kErrorNone)
280 {
281 LogInfo("Request to select better channel failed, error: %s", ErrorToString(error));
282 }
283
284 return error;
285 }
286 #endif // OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
287
StartAutoSelectTimer(void)288 void ChannelManager::StartAutoSelectTimer(void)
289 {
290 VerifyOrExit(mState == kStateIdle);
291
292 if (mAutoSelectEnabled)
293 {
294 mTimer.Start(Time::SecToMsec(mAutoSelectInterval));
295 }
296 else
297 {
298 mTimer.Stop();
299 }
300
301 exit:
302 return;
303 }
304
SetAutoChannelSelectionEnabled(bool aEnabled)305 void ChannelManager::SetAutoChannelSelectionEnabled(bool aEnabled)
306 {
307 if (aEnabled != mAutoSelectEnabled)
308 {
309 mAutoSelectEnabled = aEnabled;
310 IgnoreError(RequestChannelSelect(false));
311 StartAutoSelectTimer();
312 }
313 }
314
SetAutoChannelSelectionInterval(uint32_t aInterval)315 Error ChannelManager::SetAutoChannelSelectionInterval(uint32_t aInterval)
316 {
317 Error error = kErrorNone;
318 uint32_t prevInterval = mAutoSelectInterval;
319
320 VerifyOrExit((aInterval != 0) && (aInterval <= Time::MsecToSec(Timer::kMaxDelay)), error = kErrorInvalidArgs);
321
322 mAutoSelectInterval = aInterval;
323
324 if (mAutoSelectEnabled && (mState == kStateIdle) && mTimer.IsRunning() && (prevInterval != aInterval))
325 {
326 mTimer.StartAt(mTimer.GetFireTime() - Time::SecToMsec(prevInterval), Time::SecToMsec(aInterval));
327 }
328
329 exit:
330 return error;
331 }
332
SetSupportedChannels(uint32_t aChannelMask)333 void ChannelManager::SetSupportedChannels(uint32_t aChannelMask)
334 {
335 mSupportedChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());
336
337 LogInfo("Supported channels: %s", mSupportedChannelMask.ToString().AsCString());
338 }
339
SetFavoredChannels(uint32_t aChannelMask)340 void ChannelManager::SetFavoredChannels(uint32_t aChannelMask)
341 {
342 mFavoredChannelMask.SetMask(aChannelMask & Get<Mac::Mac>().GetSupportedChannelMask().GetMask());
343
344 LogInfo("Favored channels: %s", mFavoredChannelMask.ToString().AsCString());
345 }
346
SetCcaFailureRateThreshold(uint16_t aThreshold)347 void ChannelManager::SetCcaFailureRateThreshold(uint16_t aThreshold)
348 {
349 mCcaFailureRateThreshold = aThreshold;
350
351 LogInfo("CCA threshold: 0x%04x", mCcaFailureRateThreshold);
352 }
353
354 } // namespace Utils
355 } // namespace ot
356
357 #endif // #if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE
358