/* * 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/locator_getters.hpp" #include "common/log.hpp" #include "common/random.hpp" #include "instance/instance.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) { ClearAllBytes(aInfo); #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 preference = static_cast(old); preference += aDifference; if (preference > kMaxPreference) { preference = kMaxPreference; } if (preference < kMinPreference) { preference = kMinPreference; } aNeighbor.SetRadioPreference(aRadioType, static_cast(preference)); // 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) != (preference >= 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().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().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 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