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