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