• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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