| /* |
| * Copyright (c) 2019, The OpenThread Authors. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the copyright holder nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| /** |
| * @file |
| * This file includes implementation of radio selector (for multi radio links). |
| */ |
| |
| #include "radio_selector.hpp" |
| |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| |
| #include "common/code_utils.hpp" |
| #include "common/instance.hpp" |
| #include "common/locator_getters.hpp" |
| #include "common/log.hpp" |
| #include "common/random.hpp" |
| |
| namespace ot { |
| |
| RegisterLogModule("RadioSelector"); |
| |
| // This array defines the order in which different radio link types are |
| // selected for message tx (direct message). |
| const Mac::RadioType RadioSelector::sRadioSelectionOrder[Mac::kNumRadioTypes] = { |
| #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| Mac::kRadioTypeTrel, |
| #endif |
| #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE |
| Mac::kRadioTypeIeee802154, |
| #endif |
| }; |
| |
| RadioSelector::RadioSelector(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| { |
| } |
| |
| void RadioSelector::NeighborInfo::PopulateMultiRadioInfo(MultiRadioInfo &aInfo) |
| { |
| memset(&aInfo, 0, sizeof(MultiRadioInfo)); |
| |
| #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE |
| if (GetSupportedRadioTypes().Contains(Mac::kRadioTypeIeee802154)) |
| { |
| aInfo.mSupportsIeee802154 = true; |
| aInfo.mIeee802154Info.mPreference = GetRadioPreference(Mac::kRadioTypeIeee802154); |
| } |
| #endif |
| |
| #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| if (GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel)) |
| { |
| aInfo.mSupportsTrelUdp6 = true; |
| aInfo.mTrelUdp6Info.mPreference = GetRadioPreference(Mac::kRadioTypeTrel); |
| } |
| #endif |
| } |
| |
| LogLevel RadioSelector::UpdatePreference(Neighbor &aNeighbor, Mac::RadioType aRadioType, int16_t aDifference) |
| { |
| uint8_t old = aNeighbor.GetRadioPreference(aRadioType); |
| int16_t preferecne = static_cast<int16_t>(old); |
| |
| preferecne += aDifference; |
| |
| if (preferecne > kMaxPreference) |
| { |
| preferecne = kMaxPreference; |
| } |
| |
| if (preferecne < kMinPreference) |
| { |
| preferecne = kMinPreference; |
| } |
| |
| aNeighbor.SetRadioPreference(aRadioType, static_cast<uint8_t>(preferecne)); |
| |
| // We check whether the update to the preference value caused it |
| // to cross the threshold `kHighPreference`. Based on this we |
| // return a suggested log level. If there is cross, suggest info |
| // log level, otherwise debug log level. |
| |
| return ((old >= kHighPreference) != (preferecne >= kHighPreference)) ? kLogLevelInfo : kLogLevelDebg; |
| } |
| |
| void RadioSelector::UpdateOnReceive(Neighbor &aNeighbor, Mac::RadioType aRadioType, bool aIsDuplicate) |
| { |
| LogLevel logLevel = kLogLevelInfo; |
| |
| if (aNeighbor.GetSupportedRadioTypes().Contains(aRadioType)) |
| { |
| logLevel = UpdatePreference(aNeighbor, aRadioType, |
| aIsDuplicate ? kPreferenceChangeOnRxDuplicate : kPreferenceChangeOnRx); |
| |
| Log(logLevel, aIsDuplicate ? "UpdateOnDupRx" : "UpdateOnRx", aRadioType, aNeighbor); |
| } |
| else |
| { |
| aNeighbor.AddSupportedRadioType(aRadioType); |
| aNeighbor.SetRadioPreference(aRadioType, kInitPreference); |
| |
| Log(logLevel, "NewRadio(OnRx)", aRadioType, aNeighbor); |
| } |
| } |
| |
| void RadioSelector::UpdateOnSendDone(Mac::TxFrame &aFrame, Error aTxError) |
| { |
| LogLevel logLevel = kLogLevelInfo; |
| Mac::RadioType radioType = aFrame.GetRadioType(); |
| Mac::Address macDest; |
| Neighbor * neighbor; |
| |
| #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| if (radioType == Mac::kRadioTypeTrel) |
| { |
| // TREL radio link uses deferred ack model. We ignore |
| // `SendDone` event from `Mac` layer with success status and |
| // wait for deferred ack callback. |
| VerifyOrExit(aTxError != kErrorNone); |
| } |
| #endif |
| |
| VerifyOrExit(aFrame.GetAckRequest()); |
| |
| IgnoreError(aFrame.GetDstAddr(macDest)); |
| neighbor = Get<NeighborTable>().FindNeighbor(macDest, Neighbor::kInStateAnyExceptInvalid); |
| VerifyOrExit(neighbor != nullptr); |
| |
| if (neighbor->GetSupportedRadioTypes().Contains(radioType)) |
| { |
| logLevel = UpdatePreference( |
| *neighbor, radioType, (aTxError == kErrorNone) ? kPreferenceChangeOnTxSuccess : kPreferenceChangeOnTxError); |
| |
| Log(logLevel, (aTxError == kErrorNone) ? "UpdateOnTxSucc" : "UpdateOnTxErr", radioType, *neighbor); |
| } |
| else |
| { |
| VerifyOrExit(aTxError == kErrorNone); |
| neighbor->AddSupportedRadioType(radioType); |
| neighbor->SetRadioPreference(radioType, kInitPreference); |
| |
| Log(logLevel, "NewRadio(OnTx)", radioType, *neighbor); |
| } |
| |
| exit: |
| return; |
| } |
| |
| #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| void RadioSelector::UpdateOnDeferredAck(Neighbor &aNeighbor, Error aTxError, bool &aAllowNeighborRemove) |
| { |
| LogLevel logLevel = kLogLevelInfo; |
| |
| aAllowNeighborRemove = true; |
| |
| if (aNeighbor.GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel)) |
| { |
| logLevel = UpdatePreference(aNeighbor, Mac::kRadioTypeTrel, |
| (aTxError == kErrorNone) ? kPreferenceChangeOnDeferredAckSuccess |
| : kPreferenceChangeOnDeferredAckTimeout); |
| |
| Log(logLevel, (aTxError == kErrorNone) ? "UpdateOnDefAckSucc" : "UpdateOnDefAckFail", Mac::kRadioTypeTrel, |
| aNeighbor); |
| |
| // In case of deferred ack timeout, we check if the neighbor |
| // has any other radio link (with high preference) for future |
| // tx. If it it does, we set `aAllowNeighborRemove` to `false` |
| // to ensure neighbor is not removed yet. |
| |
| VerifyOrExit(aTxError != kErrorNone); |
| |
| for (Mac::RadioType radio : sRadioSelectionOrder) |
| { |
| if ((radio != Mac::kRadioTypeTrel) && aNeighbor.GetSupportedRadioTypes().Contains(radio) && |
| aNeighbor.GetRadioPreference(radio) >= kHighPreference) |
| { |
| aAllowNeighborRemove = false; |
| ExitNow(); |
| } |
| } |
| } |
| else |
| { |
| VerifyOrExit(aTxError == kErrorNone); |
| aNeighbor.AddSupportedRadioType(Mac::kRadioTypeTrel); |
| aNeighbor.SetRadioPreference(Mac::kRadioTypeTrel, kInitPreference); |
| |
| Log(logLevel, "NewRadio(OnDefAckSucc)", Mac::kRadioTypeTrel, aNeighbor); |
| } |
| |
| exit: |
| return; |
| } |
| #endif // OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| |
| Mac::RadioType RadioSelector::Select(Mac::RadioTypes aRadioOptions, const Neighbor &aNeighbor) |
| { |
| Mac::RadioType selectedRadio = sRadioSelectionOrder[0]; |
| uint8_t selectedPreference = 0; |
| bool found = false; |
| |
| // Select the first radio links with preference higher than |
| // threshold `kHighPreference`. The radio links are checked in the |
| // order defined by the `sRadioSelectionOrder` array. If no radio |
| // link has preference higher then threshold, select the one with |
| // highest preference. |
| |
| for (Mac::RadioType radio : sRadioSelectionOrder) |
| { |
| if (aRadioOptions.Contains(radio)) |
| { |
| uint8_t preference = aNeighbor.GetRadioPreference(radio); |
| |
| if (preference >= kHighPreference) |
| { |
| selectedRadio = radio; |
| break; |
| } |
| |
| if (!found || (selectedPreference < preference)) |
| { |
| found = true; |
| selectedRadio = radio; |
| selectedPreference = preference; |
| } |
| } |
| } |
| |
| return selectedRadio; |
| } |
| |
| Mac::TxFrame &RadioSelector::SelectRadio(Message &aMessage, const Mac::Address &aMacDest, Mac::TxFrames &aTxFrames) |
| { |
| Neighbor * neighbor; |
| Mac::RadioType selectedRadio; |
| Mac::RadioTypes selections; |
| |
| if (aMacDest.IsBroadcast() || aMacDest.IsNone()) |
| { |
| aMessage.ClearRadioType(); |
| ExitNow(selections.AddAll()); |
| } |
| |
| // If the radio type is already set when message was created we |
| // use the selected radio type. (e.g., MLE Discovery Response |
| // selects the radio link from which MLE Discovery Request is |
| // received. |
| |
| if (aMessage.IsRadioTypeSet()) |
| { |
| ExitNow(selections.Add(aMessage.GetRadioType())); |
| } |
| |
| neighbor = Get<NeighborTable>().FindNeighbor(aMacDest, Neighbor::kInStateAnyExceptInvalid); |
| |
| if ((neighbor == nullptr) || neighbor->GetSupportedRadioTypes().IsEmpty()) |
| { |
| // If we do not have a corresponding neighbor or do not yet |
| // know the supported radio types, we try sending on all radio |
| // links in parallel. As an example, such a situation can |
| // happen when recovering a non-sleepy child (sending MLE |
| // Child Update Request to it) after device itself was reset. |
| |
| aMessage.ClearRadioType(); |
| ExitNow(selections.AddAll()); |
| } |
| |
| selectedRadio = Select(neighbor->GetSupportedRadioTypes(), *neighbor); |
| selections.Add(selectedRadio); |
| |
| Log(kLogLevelDebg, "SelectRadio", selectedRadio, *neighbor); |
| |
| aMessage.SetRadioType(selectedRadio); |
| |
| // We (probabilistically) decide whether to probe on another radio |
| // link for the current frame tx. When probing we allow the same |
| // frame to be sent in parallel over multiple radio links but only |
| // care about the tx outcome (ack status) on the main selected |
| // radio link. This is done by setting the "required radio types" |
| // (`SetRequiredRadioTypes()`) to match the main selection on |
| // `aTxFrames`. We allow probe on TREL radio link if it is not |
| // currently usable (thus not selected) but is/was supported by |
| // the neighbor (i.e., we did rx/tx on TREL link from/to this |
| // neighbor in the past). The probe process helps detect whether |
| // the TREL link is usable again allowing us to switch over |
| // faster. |
| |
| #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| if (!selections.Contains(Mac::kRadioTypeTrel) && neighbor->GetSupportedRadioTypes().Contains(Mac::kRadioTypeTrel) && |
| (Random::NonCrypto::GetUint8InRange(0, 100) < kTrelProbeProbability)) |
| { |
| aTxFrames.SetRequiredRadioTypes(selections); |
| selections.Add(Mac::kRadioTypeTrel); |
| |
| Log(kLogLevelDebg, "Probe", Mac::kRadioTypeTrel, *neighbor); |
| } |
| #endif |
| |
| exit: |
| return aTxFrames.GetTxFrame(selections); |
| } |
| |
| Mac::RadioType RadioSelector::SelectPollFrameRadio(const Neighbor &aParent) |
| { |
| // This array defines the order in which different radio link types |
| // are selected for data poll frame tx. |
| static const Mac::RadioType selectionOrder[Mac::kNumRadioTypes] = { |
| #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE |
| Mac::kRadioTypeIeee802154, |
| #endif |
| #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE |
| Mac::kRadioTypeTrel, |
| #endif |
| }; |
| |
| Mac::RadioType selection = selectionOrder[0]; |
| |
| for (Mac::RadioType radio : selectionOrder) |
| { |
| if (aParent.GetSupportedRadioTypes().Contains(radio)) |
| { |
| selection = radio; |
| break; |
| } |
| } |
| |
| return selection; |
| } |
| |
| // LCOV_EXCL_START |
| |
| #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) |
| |
| void RadioSelector::Log(LogLevel aLogLevel, |
| const char * aActionText, |
| Mac::RadioType aRadioType, |
| const Neighbor &aNeighbor) |
| { |
| String<kRadioPreferenceStringSize> preferenceString; |
| bool isFirstEntry = true; |
| |
| VerifyOrExit(Instance::GetLogLevel() >= aLogLevel); |
| |
| for (Mac::RadioType radio : sRadioSelectionOrder) |
| { |
| if (aNeighbor.GetSupportedRadioTypes().Contains(radio)) |
| { |
| preferenceString.Append("%s%s:%d", isFirstEntry ? "" : " ", RadioTypeToString(radio), |
| aNeighbor.GetRadioPreference(radio)); |
| isFirstEntry = false; |
| } |
| } |
| |
| LogAt(aLogLevel, "RadioSelector: %s %s - neighbor:[%s rloc16:0x%04x radio-pref:{%s} state:%s]", aActionText, |
| RadioTypeToString(aRadioType), aNeighbor.GetExtAddress().ToString().AsCString(), aNeighbor.GetRloc16(), |
| preferenceString.AsCString(), Neighbor::StateToString(aNeighbor.GetState())); |
| |
| exit: |
| return; |
| } |
| |
| #else // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) |
| |
| void RadioSelector::Log(LogLevel, const char *, Mac::RadioType, const Neighbor &) |
| { |
| } |
| |
| #endif // #if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) |
| |
| // LCOV_EXCL_STOP |
| |
| } // namespace ot |
| |
| #endif // #if OPENTHREAD_CONFIG_MULTI_RADIO |