1 /*
2 * Copyright (c) 2019, 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 includes implementation of radio selector (for multi radio links).
32 */
33
34 #include "radio_selector.hpp"
35
36 #if OPENTHREAD_CONFIG_MULTI_RADIO
37
38 #include "common/code_utils.hpp"
39 #include "common/instance.hpp"
40 #include "common/locator_getters.hpp"
41 #include "common/log.hpp"
42 #include "common/random.hpp"
43
44 namespace ot {
45
46 RegisterLogModule("RadioSelector");
47
48 // This array defines the order in which different radio link types are
49 // selected for message tx (direct message).
50 const Mac::RadioType RadioSelector::sRadioSelectionOrder[Mac::kNumRadioTypes] = {
51 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
52 Mac::kRadioTypeTrel,
53 #endif
54 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
55 Mac::kRadioTypeIeee802154,
56 #endif
57 };
58
RadioSelector(Instance & aInstance)59 RadioSelector::RadioSelector(Instance &aInstance)
60 : InstanceLocator(aInstance)
61 {
62 }
63
PopulateMultiRadioInfo(MultiRadioInfo & aInfo)64 void RadioSelector::NeighborInfo::PopulateMultiRadioInfo(MultiRadioInfo &aInfo)
65 {
66 memset(&aInfo, 0, sizeof(MultiRadioInfo));
67
68 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
69 if (GetSupportedRadioTypes().Contains(Mac::kRadioTypeIeee802154))
70 {
71 aInfo.mSupportsIeee802154 = true;
72 aInfo.mIeee802154Info.mPreference = GetRadioPreference(Mac::kRadioTypeIeee802154);
73 }
74 #endif
75
76 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
77 if (GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel))
78 {
79 aInfo.mSupportsTrelUdp6 = true;
80 aInfo.mTrelUdp6Info.mPreference = GetRadioPreference(Mac::kRadioTypeTrel);
81 }
82 #endif
83 }
84
UpdatePreference(Neighbor & aNeighbor,Mac::RadioType aRadioType,int16_t aDifference)85 LogLevel RadioSelector::UpdatePreference(Neighbor &aNeighbor, Mac::RadioType aRadioType, int16_t aDifference)
86 {
87 uint8_t old = aNeighbor.GetRadioPreference(aRadioType);
88 int16_t preference = static_cast<int16_t>(old);
89
90 preference += aDifference;
91
92 if (preference > kMaxPreference)
93 {
94 preference = kMaxPreference;
95 }
96
97 if (preference < kMinPreference)
98 {
99 preference = kMinPreference;
100 }
101
102 aNeighbor.SetRadioPreference(aRadioType, static_cast<uint8_t>(preference));
103
104 // We check whether the update to the preference value caused it
105 // to cross the threshold `kHighPreference`. Based on this we
106 // return a suggested log level. If there is cross, suggest info
107 // log level, otherwise debug log level.
108
109 return ((old >= kHighPreference) != (preference >= kHighPreference)) ? kLogLevelInfo : kLogLevelDebg;
110 }
111
UpdateOnReceive(Neighbor & aNeighbor,Mac::RadioType aRadioType,bool aIsDuplicate)112 void RadioSelector::UpdateOnReceive(Neighbor &aNeighbor, Mac::RadioType aRadioType, bool aIsDuplicate)
113 {
114 LogLevel logLevel = kLogLevelInfo;
115
116 if (aNeighbor.GetSupportedRadioTypes().Contains(aRadioType))
117 {
118 logLevel = UpdatePreference(aNeighbor, aRadioType,
119 aIsDuplicate ? kPreferenceChangeOnRxDuplicate : kPreferenceChangeOnRx);
120
121 Log(logLevel, aIsDuplicate ? "UpdateOnDupRx" : "UpdateOnRx", aRadioType, aNeighbor);
122 }
123 else
124 {
125 aNeighbor.AddSupportedRadioType(aRadioType);
126 aNeighbor.SetRadioPreference(aRadioType, kInitPreference);
127
128 Log(logLevel, "NewRadio(OnRx)", aRadioType, aNeighbor);
129 }
130 }
131
UpdateOnSendDone(Mac::TxFrame & aFrame,Error aTxError)132 void RadioSelector::UpdateOnSendDone(Mac::TxFrame &aFrame, Error aTxError)
133 {
134 LogLevel logLevel = kLogLevelInfo;
135 Mac::RadioType radioType = aFrame.GetRadioType();
136 Mac::Address macDest;
137 Neighbor *neighbor;
138
139 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
140 if (radioType == Mac::kRadioTypeTrel)
141 {
142 // TREL radio link uses deferred ack model. We ignore
143 // `SendDone` event from `Mac` layer with success status and
144 // wait for deferred ack callback.
145 VerifyOrExit(aTxError != kErrorNone);
146 }
147 #endif
148
149 VerifyOrExit(aFrame.GetAckRequest());
150
151 IgnoreError(aFrame.GetDstAddr(macDest));
152 neighbor = Get<NeighborTable>().FindNeighbor(macDest, Neighbor::kInStateAnyExceptInvalid);
153 VerifyOrExit(neighbor != nullptr);
154
155 if (neighbor->GetSupportedRadioTypes().Contains(radioType))
156 {
157 logLevel = UpdatePreference(
158 *neighbor, radioType, (aTxError == kErrorNone) ? kPreferenceChangeOnTxSuccess : kPreferenceChangeOnTxError);
159
160 Log(logLevel, (aTxError == kErrorNone) ? "UpdateOnTxSucc" : "UpdateOnTxErr", radioType, *neighbor);
161 }
162 else
163 {
164 VerifyOrExit(aTxError == kErrorNone);
165 neighbor->AddSupportedRadioType(radioType);
166 neighbor->SetRadioPreference(radioType, kInitPreference);
167
168 Log(logLevel, "NewRadio(OnTx)", radioType, *neighbor);
169 }
170
171 exit:
172 return;
173 }
174
175 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
UpdateOnDeferredAck(Neighbor & aNeighbor,Error aTxError,bool & aAllowNeighborRemove)176 void RadioSelector::UpdateOnDeferredAck(Neighbor &aNeighbor, Error aTxError, bool &aAllowNeighborRemove)
177 {
178 LogLevel logLevel = kLogLevelInfo;
179
180 aAllowNeighborRemove = true;
181
182 if (aNeighbor.GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel))
183 {
184 logLevel = UpdatePreference(aNeighbor, Mac::kRadioTypeTrel,
185 (aTxError == kErrorNone) ? kPreferenceChangeOnDeferredAckSuccess
186 : kPreferenceChangeOnDeferredAckTimeout);
187
188 Log(logLevel, (aTxError == kErrorNone) ? "UpdateOnDefAckSucc" : "UpdateOnDefAckFail", Mac::kRadioTypeTrel,
189 aNeighbor);
190
191 // In case of deferred ack timeout, we check if the neighbor
192 // has any other radio link (with high preference) for future
193 // tx. If it it does, we set `aAllowNeighborRemove` to `false`
194 // to ensure neighbor is not removed yet.
195
196 VerifyOrExit(aTxError != kErrorNone);
197
198 for (Mac::RadioType radio : sRadioSelectionOrder)
199 {
200 if ((radio != Mac::kRadioTypeTrel) && aNeighbor.GetSupportedRadioTypes().Contains(radio) &&
201 aNeighbor.GetRadioPreference(radio) >= kHighPreference)
202 {
203 aAllowNeighborRemove = false;
204 ExitNow();
205 }
206 }
207 }
208 else
209 {
210 VerifyOrExit(aTxError == kErrorNone);
211 aNeighbor.AddSupportedRadioType(Mac::kRadioTypeTrel);
212 aNeighbor.SetRadioPreference(Mac::kRadioTypeTrel, kInitPreference);
213
214 Log(logLevel, "NewRadio(OnDefAckSucc)", Mac::kRadioTypeTrel, aNeighbor);
215 }
216
217 exit:
218 return;
219 }
220 #endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
221
Select(Mac::RadioTypes aRadioOptions,const Neighbor & aNeighbor)222 Mac::RadioType RadioSelector::Select(Mac::RadioTypes aRadioOptions, const Neighbor &aNeighbor)
223 {
224 Mac::RadioType selectedRadio = sRadioSelectionOrder[0];
225 uint8_t selectedPreference = 0;
226 bool found = false;
227
228 // Select the first radio links with preference higher than
229 // threshold `kHighPreference`. The radio links are checked in the
230 // order defined by the `sRadioSelectionOrder` array. If no radio
231 // link has preference higher then threshold, select the one with
232 // highest preference.
233
234 for (Mac::RadioType radio : sRadioSelectionOrder)
235 {
236 if (aRadioOptions.Contains(radio))
237 {
238 uint8_t preference = aNeighbor.GetRadioPreference(radio);
239
240 if (preference >= kHighPreference)
241 {
242 selectedRadio = radio;
243 break;
244 }
245
246 if (!found || (selectedPreference < preference))
247 {
248 found = true;
249 selectedRadio = radio;
250 selectedPreference = preference;
251 }
252 }
253 }
254
255 return selectedRadio;
256 }
257
SelectRadio(Message & aMessage,const Mac::Address & aMacDest,Mac::TxFrames & aTxFrames)258 Mac::TxFrame &RadioSelector::SelectRadio(Message &aMessage, const Mac::Address &aMacDest, Mac::TxFrames &aTxFrames)
259 {
260 Neighbor *neighbor;
261 Mac::RadioType selectedRadio;
262 Mac::RadioTypes selections;
263
264 if (aMacDest.IsBroadcast() || aMacDest.IsNone())
265 {
266 aMessage.ClearRadioType();
267 ExitNow(selections.AddAll());
268 }
269
270 // If the radio type is already set when message was created we
271 // use the selected radio type. (e.g., MLE Discovery Response
272 // selects the radio link from which MLE Discovery Request is
273 // received.
274
275 if (aMessage.IsRadioTypeSet())
276 {
277 ExitNow(selections.Add(aMessage.GetRadioType()));
278 }
279
280 neighbor = Get<NeighborTable>().FindNeighbor(aMacDest, Neighbor::kInStateAnyExceptInvalid);
281
282 if ((neighbor == nullptr) || neighbor->GetSupportedRadioTypes().IsEmpty())
283 {
284 // If we do not have a corresponding neighbor or do not yet
285 // know the supported radio types, we try sending on all radio
286 // links in parallel. As an example, such a situation can
287 // happen when recovering a non-sleepy child (sending MLE
288 // Child Update Request to it) after device itself was reset.
289
290 aMessage.ClearRadioType();
291 ExitNow(selections.AddAll());
292 }
293
294 selectedRadio = Select(neighbor->GetSupportedRadioTypes(), *neighbor);
295 selections.Add(selectedRadio);
296
297 Log(kLogLevelDebg, "SelectRadio", selectedRadio, *neighbor);
298
299 aMessage.SetRadioType(selectedRadio);
300
301 // We (probabilistically) decide whether to probe on another radio
302 // link for the current frame tx. When probing we allow the same
303 // frame to be sent in parallel over multiple radio links but only
304 // care about the tx outcome (ack status) on the main selected
305 // radio link. This is done by setting the "required radio types"
306 // (`SetRequiredRadioTypes()`) to match the main selection on
307 // `aTxFrames`. We allow probe on TREL radio link if it is not
308 // currently usable (thus not selected) but is/was supported by
309 // the neighbor (i.e., we did rx/tx on TREL link from/to this
310 // neighbor in the past). The probe process helps detect whether
311 // the TREL link is usable again allowing us to switch over
312 // faster.
313
314 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
315 if (!selections.Contains(Mac::kRadioTypeTrel) && neighbor->GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel) &&
316 (Random::NonCrypto::GetUint8InRange(0, 100) < kTrelProbeProbability))
317 {
318 aTxFrames.SetRequiredRadioTypes(selections);
319 selections.Add(Mac::kRadioTypeTrel);
320
321 Log(kLogLevelDebg, "Probe", Mac::kRadioTypeTrel, *neighbor);
322 }
323 #endif
324
325 exit:
326 return aTxFrames.GetTxFrame(selections);
327 }
328
SelectPollFrameRadio(const Neighbor & aParent)329 Mac::RadioType RadioSelector::SelectPollFrameRadio(const Neighbor &aParent)
330 {
331 // This array defines the order in which different radio link types
332 // are selected for data poll frame tx.
333 static const Mac::RadioType selectionOrder[Mac::kNumRadioTypes] = {
334 #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE
335 Mac::kRadioTypeIeee802154,
336 #endif
337 #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE
338 Mac::kRadioTypeTrel,
339 #endif
340 };
341
342 Mac::RadioType selection = selectionOrder[0];
343
344 for (Mac::RadioType radio : selectionOrder)
345 {
346 if (aParent.GetSupportedRadioTypes().Contains(radio))
347 {
348 selection = radio;
349 break;
350 }
351 }
352
353 return selection;
354 }
355
356 // LCOV_EXCL_START
357
358 #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
359
Log(LogLevel aLogLevel,const char * aActionText,Mac::RadioType aRadioType,const Neighbor & aNeighbor)360 void RadioSelector::Log(LogLevel aLogLevel,
361 const char *aActionText,
362 Mac::RadioType aRadioType,
363 const Neighbor &aNeighbor)
364 {
365 String<kRadioPreferenceStringSize> preferenceString;
366 bool isFirstEntry = true;
367
368 VerifyOrExit(Instance::GetLogLevel() >= aLogLevel);
369
370 for (Mac::RadioType radio : sRadioSelectionOrder)
371 {
372 if (aNeighbor.GetSupportedRadioTypes().Contains(radio))
373 {
374 preferenceString.Append("%s%s:%d", isFirstEntry ? "" : " ", RadioTypeToString(radio),
375 aNeighbor.GetRadioPreference(radio));
376 isFirstEntry = false;
377 }
378 }
379
380 LogAt(aLogLevel, "RadioSelector: %s %s - neighbor:[%s rloc16:0x%04x radio-pref:{%s} state:%s]", aActionText,
381 RadioTypeToString(aRadioType), aNeighbor.GetExtAddress().ToString().AsCString(), aNeighbor.GetRloc16(),
382 preferenceString.AsCString(), Neighbor::StateToString(aNeighbor.GetState()));
383
384 exit:
385 return;
386 }
387
388 #else // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
389
Log(LogLevel,const char *,Mac::RadioType,const Neighbor &)390 void RadioSelector::Log(LogLevel, const char *, Mac::RadioType, const Neighbor &) {}
391
392 #endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO)
393
394 // LCOV_EXCL_STOP
395
396 } // namespace ot
397
398 #endif // #if OPENTHREAD_CONFIG_MULTI_RADIO
399