| /* |
| * Copyright (c) 2020, 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. |
| */ |
| |
| #include "csl_tx_scheduler.hpp" |
| |
| #if OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE |
| |
| #include "common/locator_getters.hpp" |
| #include "common/log.hpp" |
| #include "common/time.hpp" |
| #include "mac/mac.hpp" |
| |
| namespace ot { |
| |
| RegisterLogModule("CslTxScheduler"); |
| |
| CslTxScheduler::Callbacks::Callbacks(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| { |
| } |
| |
| inline Error CslTxScheduler::Callbacks::PrepareFrameForChild(Mac::TxFrame &aFrame, |
| FrameContext &aContext, |
| Child & aChild) |
| { |
| return Get<IndirectSender>().PrepareFrameForChild(aFrame, aContext, aChild); |
| } |
| |
| inline void CslTxScheduler::Callbacks::HandleSentFrameToChild(const Mac::TxFrame &aFrame, |
| const FrameContext &aContext, |
| Error aError, |
| Child & aChild) |
| { |
| Get<IndirectSender>().HandleSentFrameToChild(aFrame, aContext, aError, aChild); |
| } |
| |
| //--------------------------------------------------------- |
| |
| CslTxScheduler::CslTxScheduler(Instance &aInstance) |
| : InstanceLocator(aInstance) |
| , mCslTxChild(nullptr) |
| , mCslTxMessage(nullptr) |
| , mFrameContext() |
| , mCallbacks(aInstance) |
| { |
| InitFrameRequestAhead(); |
| } |
| |
| void CslTxScheduler::InitFrameRequestAhead(void) |
| { |
| uint32_t busSpeedHz = otPlatRadioGetBusSpeed(&GetInstance()); |
| // longest frame on bus is 127 bytes with some metadata, use 150 bytes for bus Tx time estimation |
| uint32_t busTxTimeUs = ((busSpeedHz == 0) ? 0 : (150 * 8 * 1000000 + busSpeedHz - 1) / busSpeedHz); |
| |
| mCslFrameRequestAheadUs = OPENTHREAD_CONFIG_MAC_CSL_REQUEST_AHEAD_US + busTxTimeUs; |
| } |
| |
| void CslTxScheduler::Update(void) |
| { |
| if (mCslTxMessage == nullptr) |
| { |
| RescheduleCslTx(); |
| } |
| else if ((mCslTxChild != nullptr) && (mCslTxChild->GetIndirectMessage() != mCslTxMessage)) |
| { |
| // `Mac` has already started the CSL tx, so wait for tx done callback |
| // to call `RescheduleCslTx` |
| mCslTxChild = nullptr; |
| mFrameContext.mMessageNextOffset = 0; |
| } |
| } |
| |
| void CslTxScheduler::Clear(void) |
| { |
| for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid)) |
| { |
| child.ResetCslTxAttempts(); |
| child.SetCslSynchronized(false); |
| child.SetCslChannel(0); |
| child.SetCslTimeout(0); |
| child.SetCslPeriod(0); |
| child.SetCslPhase(0); |
| child.SetCslLastHeard(TimeMilli(0)); |
| } |
| |
| mFrameContext.mMessageNextOffset = 0; |
| mCslTxChild = nullptr; |
| mCslTxMessage = nullptr; |
| } |
| |
| /** |
| * This method always finds the most recent CSL tx among all children, |
| * and requests `Mac` to do CSL tx at specific time. It shouldn't be called |
| * when `Mac` is already starting to do the CSL tx (indicated by `mCslTxMessage`). |
| * |
| */ |
| void CslTxScheduler::RescheduleCslTx(void) |
| { |
| uint32_t minDelayTime = Time::kMaxDuration; |
| Child * bestChild = nullptr; |
| |
| for (Child &child : Get<ChildTable>().Iterate(Child::kInStateAnyExceptInvalid)) |
| { |
| uint32_t delay; |
| uint32_t cslTxDelay; |
| |
| if (!child.IsCslSynchronized() || child.GetIndirectMessageCount() == 0) |
| { |
| continue; |
| } |
| |
| delay = GetNextCslTransmissionDelay(child, cslTxDelay); |
| |
| if (delay < minDelayTime) |
| { |
| minDelayTime = delay; |
| bestChild = &child; |
| } |
| } |
| |
| if (bestChild != nullptr) |
| { |
| Get<Mac::Mac>().RequestCslFrameTransmission(minDelayTime / 1000UL); |
| } |
| |
| mCslTxChild = bestChild; |
| } |
| |
| uint32_t CslTxScheduler::GetNextCslTransmissionDelay(const Child &aChild, uint32_t &aDelayFromLastRx) const |
| { |
| uint64_t radioNow = otPlatRadioGetNow(&GetInstance()); |
| uint32_t periodInUs = aChild.GetCslPeriod() * kUsPerTenSymbols; |
| uint64_t firstTxWindow = aChild.GetLastRxTimestamp() + aChild.GetCslPhase() * kUsPerTenSymbols; |
| uint64_t nextTxWindow = radioNow - (radioNow % periodInUs) + (firstTxWindow % periodInUs); |
| |
| while (nextTxWindow < radioNow + mCslFrameRequestAheadUs) nextTxWindow += periodInUs; |
| |
| aDelayFromLastRx = static_cast<uint32_t>(nextTxWindow - aChild.GetLastRxTimestamp()); |
| |
| return static_cast<uint32_t>(nextTxWindow - radioNow - mCslFrameRequestAheadUs); |
| } |
| |
| #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE |
| |
| Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &aTxFrames) |
| { |
| Mac::TxFrame *frame = nullptr; |
| uint32_t txDelay; |
| |
| VerifyOrExit(mCslTxChild != nullptr); |
| |
| #if OPENTHREAD_CONFIG_MULTI_RADIO |
| frame = &aTxFrames.GetTxFrame(Mac::kRadioTypeIeee802154); |
| #else |
| frame = &aTxFrames.GetTxFrame(); |
| #endif |
| |
| VerifyOrExit(mCallbacks.PrepareFrameForChild(*frame, mFrameContext, *mCslTxChild) == kErrorNone, frame = nullptr); |
| mCslTxMessage = mCslTxChild->GetIndirectMessage(); |
| VerifyOrExit(mCslTxMessage != nullptr, frame = nullptr); |
| |
| if (mCslTxChild->GetIndirectTxAttempts() > 0 || mCslTxChild->GetCslTxAttempts() > 0) |
| { |
| // For a re-transmission of an indirect frame to a sleepy |
| // child, we ensure to use the same frame counter, key id, and |
| // data sequence number as the previous attempt. |
| |
| frame->SetIsARetransmission(true); |
| frame->SetSequence(mCslTxChild->GetIndirectDataSequenceNumber()); |
| |
| if (frame->GetSecurityEnabled()) |
| { |
| frame->SetFrameCounter(mCslTxChild->GetIndirectFrameCounter()); |
| frame->SetKeyId(mCslTxChild->GetIndirectKeyId()); |
| } |
| } |
| else |
| { |
| frame->SetIsARetransmission(false); |
| } |
| |
| frame->SetChannel(mCslTxChild->GetCslChannel() == 0 ? Get<Mac::Mac>().GetPanChannel() |
| : mCslTxChild->GetCslChannel()); |
| |
| GetNextCslTransmissionDelay(*mCslTxChild, txDelay); |
| frame->SetTxDelay(txDelay); |
| frame->SetTxDelayBaseTime( |
| static_cast<uint32_t>(mCslTxChild->GetLastRxTimestamp())); // Only LSB part of the time is required. |
| frame->SetCsmaCaEnabled(false); |
| |
| exit: |
| return frame; |
| } |
| |
| #else // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE |
| |
| Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &) |
| { |
| return nullptr; |
| } |
| |
| #endif // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE |
| |
| void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError) |
| { |
| Child *child = mCslTxChild; |
| |
| mCslTxMessage = nullptr; |
| |
| VerifyOrExit(child != nullptr); // The result is no longer interested by upper layer |
| |
| mCslTxChild = nullptr; |
| |
| HandleSentFrame(aFrame, aError, *child); |
| |
| exit: |
| return; |
| } |
| |
| void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, Child &aChild) |
| { |
| switch (aError) |
| { |
| case kErrorNone: |
| aChild.ResetCslTxAttempts(); |
| aChild.ResetIndirectTxAttempts(); |
| break; |
| |
| case kErrorNoAck: |
| OT_ASSERT(!aFrame.GetSecurityEnabled() || aFrame.IsHeaderUpdated()); |
| |
| aChild.IncrementCslTxAttempts(); |
| LogInfo("CSL tx to child %04x failed, attempt %d/%d", aChild.GetRloc16(), aChild.GetCslTxAttempts(), |
| kMaxCslTriggeredTxAttempts); |
| |
| if (aChild.GetCslTxAttempts() >= kMaxCslTriggeredTxAttempts) |
| { |
| // CSL transmission attempts reach max, consider child out of sync |
| aChild.SetCslSynchronized(false); |
| aChild.ResetCslTxAttempts(); |
| } |
| |
| OT_FALL_THROUGH; |
| |
| case kErrorChannelAccessFailure: |
| case kErrorAbort: |
| |
| // Even if CSL tx attempts count reaches max, the message won't be |
| // dropped until indirect tx attempts count reaches max. So here it |
| // would set sequence number and schedule next CSL tx. |
| |
| if (!aFrame.IsEmpty()) |
| { |
| aChild.SetIndirectDataSequenceNumber(aFrame.GetSequence()); |
| |
| if (aFrame.GetSecurityEnabled() && aFrame.IsHeaderUpdated()) |
| { |
| uint32_t frameCounter; |
| uint8_t keyId; |
| |
| IgnoreError(aFrame.GetFrameCounter(frameCounter)); |
| aChild.SetIndirectFrameCounter(frameCounter); |
| |
| IgnoreError(aFrame.GetKeyId(keyId)); |
| aChild.SetIndirectKeyId(keyId); |
| } |
| } |
| |
| RescheduleCslTx(); |
| ExitNow(); |
| |
| default: |
| OT_ASSERT(false); |
| OT_UNREACHABLE_CODE(break); |
| } |
| |
| mCallbacks.HandleSentFrameToChild(aFrame, mFrameContext, aError, aChild); |
| |
| exit: |
| return; |
| } |
| |
| } // namespace ot |
| |
| #endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE |