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