diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index 51554a6486c..191b25a4864 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -641,6 +641,8 @@ openthread_core_files = [ "thread/dua_manager.hpp", "thread/energy_scan_server.cpp", "thread/energy_scan_server.hpp", + "thread/enh_csl_sender.cpp", + "thread/enh_csl_sender.hpp", "thread/indirect_sender.cpp", "thread/indirect_sender.hpp", "thread/indirect_sender_frame_context.hpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 26dec232b60..bdbeffd4a19 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -213,6 +213,7 @@ set(COMMON_SOURCES thread/discover_scanner.cpp thread/dua_manager.cpp thread/energy_scan_server.cpp + thread/enh_csl_sender.cpp thread/indirect_sender.cpp thread/key_manager.cpp thread/link_metrics.cpp diff --git a/src/core/common/locator.hpp b/src/core/common/locator.hpp index 8b1f8221742..0e8c0e04521 100644 --- a/src/core/common/locator.hpp +++ b/src/core/common/locator.hpp @@ -81,7 +81,7 @@ template class GetProvider * * @returns A reference to the `Type` object of the instance. */ - template inline Type &Get(void) const; // Implemented in `locator_getters.hpp`. + template inline Type &Get(void) const; protected: GetProvider(void) = default; diff --git a/src/core/config/mac.h b/src/core/config/mac.h index d2359a91db2..84db7472804 100644 --- a/src/core/config/mac.h +++ b/src/core/config/mac.h @@ -558,6 +558,25 @@ #define OPENTHREAD_CONFIG_MAC_DATA_POLL_TIMEOUT 100 #endif +/** + * @def OPENTHREAD_CONFIG_MAC_CSL_WAKEUP_INTERVAL + * + * Periodicity of wake-up frame transmission by WC (in units of 10 symbols). + */ +#ifndef OPENTHREAD_CONFIG_MAC_CSL_WAKEUP_INTERVAL +#define OPENTHREAD_CONFIG_MAC_CSL_WAKEUP_INTERVAL 47 +#endif + +/** + * @def OPENTHREAD_CONFIG_MAC_ENH_CSL_TX_ATTEMPTS + * + * Maximum number of TX attempts for the enhanced CSL communication before considering the peer de-synchronized. + * + */ +#ifndef OPENTHREAD_CONFIG_MAC_ENH_CSL_TX_ATTEMPTS +#define OPENTHREAD_CONFIG_MAC_ENH_CSL_TX_ATTEMPTS 8 +#endif + /** * @} */ diff --git a/src/core/instance/instance.hpp b/src/core/instance/instance.hpp index 188609b4b6c..228d228ec6e 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -835,6 +835,10 @@ template <> inline MeshCoP::Leader &Instance::Get(void) { return mLeader; } template <> inline MeshCoP::JoinerRouter &Instance::Get(void) { return mJoinerRouter; } #endif // OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +template <> inline EnhCslSender &Instance::Get(void) { return mMeshForwarder.mEnhCslSender; } +#endif + template <> inline AnnounceBeginServer &Instance::Get(void) { return mAnnounceBegin; } template <> inline DataPollSender &Instance::Get(void) { return mMeshForwarder.mDataPollSender; } diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index 6255a463c8c..420ec432adb 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -199,6 +199,9 @@ bool Mac::IsInTransmitState(void) const #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE case kOperationTransmitDataCsl: #endif +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + case kOperationTransmitDataEnhCsl: #endif case kOperationTransmitBeacon: case kOperationTransmitPoll: @@ -517,6 +520,20 @@ void Mac::RequestCslFrameTransmission(uint32_t aDelay) #endif #endif // OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +void Mac::RequestEnhCslFrameTransmission(uint32_t aDelay) +{ + VerifyOrExit(mEnabled); + + mEnhCslTxFireTime = TimerMilli::GetNow() + aDelay; + + StartOperation(kOperationTransmitDataEnhCsl); + +exit: + return; +} +#endif + Error Mac::RequestDataPollTransmission(void) { Error error = kErrorNone; @@ -542,6 +559,13 @@ void Mac::UpdateIdleMode(void) VerifyOrExit(mOperation == kOperationIdle); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (IsPending(kOperationTransmitDataEnhCsl)) + { + mTimer.FireAt(mEnhCslTxFireTime); + } +#endif + if (!mRxOnWhenIdle) { #if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS @@ -642,6 +666,12 @@ void Mac::PerformNextOperation(void) { mOperation = kOperationTransmitDataCsl; } +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + else if (IsPending(kOperationTransmitDataEnhCsl) && TimerMilli::GetNow() >= mEnhCslTxFireTime) + { + mOperation = kOperationTransmitDataEnhCsl; + } #endif else if (IsPending(kOperationActiveScan)) { @@ -706,6 +736,9 @@ void Mac::PerformNextOperation(void) #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE case kOperationTransmitDataCsl: #endif +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + case kOperationTransmitDataEnhCsl: #endif case kOperationTransmitPoll: BeginTransmit(); @@ -1019,6 +1052,23 @@ void Mac::BeginTransmit(void) #endif #endif // OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + case kOperationTransmitDataEnhCsl: + txFrames.SetChannel(mRadioChannel); + txFrames.SetMaxCsmaBackoffs(kMaxCsmaBackoffsCsl); + txFrames.SetMaxFrameRetries(kMaxFrameRetriesCsl); + frame = Get().HandleFrameRequest(txFrames); + VerifyOrExit(frame != nullptr); + + // If the frame is marked as retransmission, then data sequence number is already set. + if (!frame->IsARetransmission()) + { + frame->SetSequence(mDataSequence++); + } + + break; +#endif + default: OT_ASSERT(false); } @@ -1453,7 +1503,6 @@ void Mac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError) mRetryHistogram.mTxIndirectRetrySuccess[mLinks.GetTransmitRetries()]++; } #endif - DumpDebg("TX", aFrame.GetHeader(), aFrame.GetLength()); FinishOperation(); Get().HandleSentFrame(aFrame, aError); @@ -1461,6 +1510,18 @@ void Mac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aError) break; #endif // OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + case kOperationTransmitDataEnhCsl: + mCounters.mTxData++; + + DumpDebg("TX", aFrame.GetHeader(), aFrame.GetLength()); + FinishOperation(); + Get().HandleSentFrame(aFrame, aError); + PerformNextOperation(); + + break; +#endif + default: OT_ASSERT(false); } @@ -1487,6 +1548,12 @@ void Mac::HandleTimer(void) break; case kOperationIdle: +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (IsPending(kOperationTransmitDataEnhCsl)) + { + PerformNextOperation(); + } +#endif if (!mRxOnWhenIdle) { #if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS @@ -1602,7 +1669,8 @@ Error Mac::ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neig { uint32_t sequence; - // TODO: Avoid generating a new key if a wake-up frame was recently received already + // Avoid generating a new key if a wake-up frame was recently received already + VerifyOrExit(!Get().IsWakeupCoordPresent(), error = kErrorInvalidState); IgnoreError(aFrame.GetKeyId(keyid)); sequence = BigEndian::ReadUint32(aFrame.GetKeySource()); @@ -2207,6 +2275,9 @@ const char *Mac::OperationToString(Operation aOperation) #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE "TransmitDataCsl", // (8) kOperationTransmitDataCsl #endif +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + "TransmitDataEnhCsl", // kOperationTransmitDataEnhCsl #endif }; @@ -2525,7 +2596,8 @@ void Mac::UpdateWakeupListening(void) Error Mac::HandleWakeupFrame(const RxFrame &aFrame) { - Error error = kErrorNone; + Error error = kErrorNone; + constexpr uint32_t kWakeupIntervalUs = kDefaultWedListenInterval * kUsPerTenSymbols; const ConnectionIe *connectionIe; uint32_t rvTimeUs; uint64_t rvTimestampUs; @@ -2533,6 +2605,8 @@ Error Mac::HandleWakeupFrame(const RxFrame &aFrame) uint64_t radioNowUs; uint8_t retryInterval; uint8_t retryCount; + Neighbor *coord; + Address coordAddress; VerifyOrExit(mWakeupListenEnabled && aFrame.IsWakeupFrame()); connectionIe = aFrame.GetConnectionIe(); @@ -2566,8 +2640,21 @@ Error Mac::HandleWakeupFrame(const RxFrame &aFrame) // Stop receiving more wake up frames IgnoreError(SetWakeupListenEnabled(false)); - // TODO: start MLE attach process with the WC - OT_UNUSED_VARIABLE(attachDelayMs); + IgnoreError(aFrame.GetSrcAddr(coordAddress)); + Get().InitParentCandidate(coordAddress.GetExtended()); + coord = &Get().GetParentCandidate(); + coord->SetEnhCslPeriod(kDefaultWedListenInterval * retryInterval); + coord->SetEnhCslPhase(0); + coord->SetEnhCslSynchronized(true); + coord->SetEnhCslLastHeard(TimerMilli::GetNow()); + // Rendezvous time is the time when the WC begins listening for the connection request from the awakened WED. + // Since EnhCslSender schedules a frame's PHR at `LastRxTimestamp + Phase + n*Period`, increase the timestamp + // by SHR length and the CSL uncertainty to make sure SHR begins while the WC is already listening. + coord->SetEnhLastRxTimestamp(rvTimestampUs + kRadioHeaderShrDuration + Get().GetCslUncertainty() * 10); + coord->SetEnhCslMaxTxAttempts(retryCount); + + Get().AttachToWakeupCoord(coordAddress.GetExtended(), TimerMilli::GetNow() + attachDelayMs, + kWakeupIntervalUs * retryInterval * retryCount / 1000); exit: return error; diff --git a/src/core/mac/mac.hpp b/src/core/mac/mac.hpp index 4a1c0d1953c..83b09e2e17c 100644 --- a/src/core/mac/mac.hpp +++ b/src/core/mac/mac.hpp @@ -91,6 +91,7 @@ constexpr uint16_t kMinCslIePeriod = OPENTHREAD_CONFIG_MAC_CSL_MIN_PERIOD; constexpr uint32_t kDefaultWedListenInterval = OPENTHREAD_CONFIG_WED_LISTEN_INTERVAL; constexpr uint32_t kDefaultWedListenDuration = OPENTHREAD_CONFIG_WED_LISTEN_DURATION; +constexpr uint16_t kDefaultWakeupInterval = OPENTHREAD_CONFIG_MAC_CSL_WAKEUP_INTERVAL; /** * Defines the function pointer called on receiving an IEEE 802.15.4 Beacon during an Active Scan. @@ -213,6 +214,16 @@ class Mac : public InstanceLocator, private NonCopyable void RequestCslFrameTransmission(uint32_t aDelay); #endif +#endif + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + /** + * Requests `Mac` to start an enhanced CSL tx operation after a delay of @p aDelay time. + * + * @param[in] aDelay Delay time for `Mac` to start an enhanced CSL tx, in units of milliseconds. + * + */ + void RequestEnhCslFrameTransmission(uint32_t aDelay); #endif /** @@ -762,6 +773,9 @@ class Mac : public InstanceLocator, private NonCopyable #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE kOperationTransmitDataCsl, #endif +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + kOperationTransmitDataEnhCsl, #endif }; @@ -883,8 +897,9 @@ class Mac : public InstanceLocator, private NonCopyable #endif uint8_t mWakeupChannel; #if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE - uint32_t mWakeupListenInterval; - uint32_t mWakeupListenDuration; + uint32_t mWakeupListenInterval; + uint32_t mWakeupListenDuration; + TimeMilli mEnhCslTxFireTime; #endif union { diff --git a/src/core/thread/enh_csl_sender.cpp b/src/core/thread/enh_csl_sender.cpp new file mode 100644 index 00000000000..36a597de16f --- /dev/null +++ b/src/core/thread/enh_csl_sender.cpp @@ -0,0 +1,444 @@ +/* + * Copyright (c) 2024, 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 "enh_csl_sender.hpp" + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + +#include "common/log.hpp" +#include "common/time.hpp" +#include "instance/instance.hpp" +#include "mac/mac.hpp" + +namespace ot { + +RegisterLogModule("EnhCslSender"); + +EnhCslSender::EnhCslSender(Instance &aInstance) + : InstanceLocator(aInstance) + , mCslTxNeigh(nullptr) + , mFrameContext() +{ + InitFrameRequestAhead(); +} + +Neighbor *EnhCslSender::GetParent(void) const +{ + Neighbor *parent = nullptr; + + if (Get().GetParent().IsStateValid()) + { + parent = &Get().GetParent(); + } + else if (Get().IsWakeupCoordPresent()) + { + parent = &Get().GetParentCandidate(); + } + + return parent; +} + +void EnhCslSender::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 EnhCslSender::AddMessageForCslPeer(Message &aMessage, Neighbor &aNeighbor) +{ + // TODO: find a proper way to mark this message as directed for the neighbor in CSL mode. + // By the moment use IsDirectTransmission which defaults to false, assuming a single CSL peer. + + if (mCslTxNeigh->GetIndirectMessage() == nullptr) + { + aNeighbor.SetIndirectMessage(&aMessage); + aNeighbor.SetIndirectFragmentOffset(0); + } + aNeighbor.IncrementIndirectMessageCount(); + RescheduleCslTx(); +} + +void EnhCslSender::ClearAllMessagesForCslPeer(Neighbor &aNeighbor) +{ + VerifyOrExit(aNeighbor.GetIndirectMessageCount() > 0); + + for (Message &message : Get().mSendQueue) + { + Get().RemoveMessageIfNoPendingTx(message); + } + + aNeighbor.SetIndirectMessage(nullptr); + aNeighbor.ResetIndirectMessageCount(); + aNeighbor.ResetEnhCslTxAttempts(); + + Update(); + +exit: + return; +} + +void EnhCslSender::Update(void) +{ + if (mCslTxMessage == nullptr) + { + RescheduleCslTx(); + } + else if ((mCslTxNeigh != nullptr) && (mCslTxNeigh->GetIndirectMessage() != mCslTxMessage)) + { + // `Mac` has already started the CSL tx, so wait for tx done callback + // to call `RescheduleCslTx` + mCslTxNeigh = nullptr; + mFrameContext.mMessageNextOffset = 0; + } +} + +/** + * This method assumes that there is a single enhanced CSL synchronized neighbor + * and that if any message is not marked as direct transmission then it should be sent via + * enhanced CSL transmission. + * + */ +void EnhCslSender::RescheduleCslTx(void) +{ + uint32_t delay; + uint32_t cslTxDelay; + + // TODO: go over the list of neighbors awaiting indirect transmission + mCslTxNeigh = GetParent(); + + VerifyOrExit(mCslTxNeigh->GetIndirectMessageCount() > 0); + + if (mCslTxNeigh->GetIndirectMessage() == nullptr) + { + for (Message &message : Get().mSendQueue) + { + if (!message.IsDirectTransmission()) + { + mCslTxNeigh->SetIndirectMessage(&message); + mCslTxNeigh->SetIndirectFragmentOffset(0); + break; + } + } + } + + /* + * If no indirect message could be found despite of the positive indirect + * message counter, then some messages must have been removed from the send + * queue without notifying the enhanced CSL sender. Until this notification + * is implemented, reset the counter to recover from such a scenario. + */ + VerifyOrExit(mCslTxNeigh->GetIndirectMessage() != nullptr, mCslTxNeigh->ResetIndirectMessageCount()); + + delay = GetNextCslTransmissionDelay(*mCslTxNeigh, cslTxDelay, mCslFrameRequestAheadUs); + Get().RequestEnhCslFrameTransmission(delay / 1000UL); + +exit: + return; +} + +uint32_t EnhCslSender::GetNextCslTransmissionDelay(Neighbor &aNeighbor, + uint32_t &aDelayFromLastRx, + uint32_t aAheadUs) const +{ + uint64_t radioNow = otPlatRadioGetNow(&GetInstance()); + uint32_t periodInUs = aNeighbor.GetEnhCslPeriod() * kUsPerTenSymbols; + uint64_t firstTxWindow = aNeighbor.GetEnhLastRxTimestamp() + aNeighbor.GetEnhCslPhase() * kUsPerTenSymbols; + uint64_t nextTxWindow = radioNow - (radioNow % periodInUs) + (firstTxWindow % periodInUs); + + while (nextTxWindow < radioNow + aAheadUs) nextTxWindow += periodInUs; + + aDelayFromLastRx = static_cast(nextTxWindow - aNeighbor.GetEnhLastRxTimestamp()); + + return static_cast(nextTxWindow - radioNow - aAheadUs); +} + +uint16_t EnhCslSender::PrepareDataFrame(Mac::TxFrame &aFrame, Neighbor &aNeighbor, Message &aMessage) +{ + Ip6::Header ip6Header; + Mac::Addresses macAddrs; + uint16_t directTxOffset; + uint16_t nextOffset; + + // Determine the MAC source and destination addresses. + + IgnoreError(aMessage.Read(0, ip6Header)); + + Get().GetMacSourceAddress(ip6Header.GetSource(), macAddrs.mSource); + + if (ip6Header.GetDestination().IsLinkLocalUnicast()) + { + Get().GetMacDestinationAddress(ip6Header.GetDestination(), macAddrs.mDestination); + } + else + { + macAddrs.mDestination.SetExtended(aNeighbor.GetExtAddress()); + } + + // Prepare the data frame from previous neighbor's indirect offset. + + directTxOffset = aMessage.GetOffset(); + aMessage.SetOffset(aNeighbor.GetIndirectFragmentOffset()); + + nextOffset = Get().PrepareDataFrameWithNoMeshHeader(aFrame, aMessage, macAddrs); + + aMessage.SetOffset(directTxOffset); + + // Intentionally not setting frame pending bit even if more messages are queued + + return nextOffset; +} + +Error EnhCslSender::PrepareFrameForNeighbor(Mac::TxFrame &aFrame, FrameContext &aContext, Neighbor &aNeighbor) +{ + Error error = kErrorNone; + Message *message = aNeighbor.GetIndirectMessage(); + VerifyOrExit(message != nullptr, error = kErrorInvalidState); + + switch (message->GetType()) + { + case Message::kTypeIp6: + aContext.mMessageNextOffset = PrepareDataFrame(aFrame, aNeighbor, *message); + break; + + default: + error = kErrorNotImplemented; + break; + } + +exit: + return error; +} + +#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE + +Mac::TxFrame *EnhCslSender::HandleFrameRequest(Mac::TxFrames &aTxFrames) +{ + Mac::TxFrame *frame = nullptr; + uint32_t txDelay; + uint32_t delay; + bool isCsl = false; + + VerifyOrExit(mCslTxNeigh != nullptr); + VerifyOrExit(mCslTxNeigh->IsEnhCslSynchronized()); + +#if OPENTHREAD_CONFIG_MULTI_RADIO + frame = &aTxFrames.GetTxFrame(Mac::kRadioTypeIeee802154); +#else + frame = &aTxFrames.GetTxFrame(); +#endif + + VerifyOrExit(PrepareFrameForNeighbor(*frame, mFrameContext, *mCslTxNeigh) == kErrorNone, frame = nullptr); + mCslTxMessage = mCslTxNeigh->GetIndirectMessage(); + VerifyOrExit(mCslTxMessage != nullptr, frame = nullptr); + + if (mCslTxNeigh->GetEnhCslTxAttempts() > 0) + { + // For a re-transmission of an indirect frame to a sleepy + // neighbor, we ensure to use the same frame counter, key id, and + // data sequence number as the previous attempt. + + frame->SetIsARetransmission(true); + frame->SetSequence(mCslTxNeigh->GetIndirectDataSequenceNumber()); + + // If the frame contains CSL IE, it must be refreshed and re-secured with a new frame counter. + // See Thread 1.3.0 Specification, 3.2.6.3.7 CSL Retransmissions +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + isCsl = frame->IsCslIePresent(); +#endif + + if (frame->GetSecurityEnabled() && !isCsl) + { + frame->SetFrameCounter(mCslTxNeigh->GetIndirectFrameCounter()); + frame->SetKeyId(mCslTxNeigh->GetIndirectKeyId()); + } + } + else + { + frame->SetIsARetransmission(false); + } + + // Use zero as aAheadUs not to miss a CSL slot in the case the MAC operation is slightly delayed. + // This code mimics CslTxScheduler::HandleFrameRequest so see the latter for more details. + delay = GetNextCslTransmissionDelay(*mCslTxNeigh, txDelay, /* aAheadUs */ 0); + VerifyOrExit(delay <= mCslFrameRequestAheadUs + kFramePreparationGuardInterval, frame = nullptr); + + frame->SetTxDelay(txDelay); + frame->SetTxDelayBaseTime( + static_cast(mCslTxNeigh->GetEnhLastRxTimestamp())); // 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 *EnhCslSender::HandleFrameRequest(Mac::TxFrames &) { return nullptr; } + +#endif // OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE + +void EnhCslSender::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError) +{ + Neighbor *neighbor = mCslTxNeigh; + + mCslTxMessage = nullptr; + + VerifyOrExit(neighbor != nullptr); // The result is no longer interested by upper layer + + mCslTxNeigh = nullptr; + + HandleSentFrame(aFrame, aError, *neighbor); + +exit: + return; +} + +void EnhCslSender::HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, Neighbor &aNeighbor) +{ + switch (aError) + { + case kErrorNone: + aNeighbor.ResetEnhCslTxAttempts(); + break; + + case kErrorNoAck: + OT_ASSERT(!aFrame.GetSecurityEnabled() || aFrame.IsHeaderUpdated()); + + aNeighbor.IncrementEnhCslTxAttempts(); + LogInfo("CSL tx to neighbor %04x failed, attempt %d/%d", aNeighbor.GetRloc16(), aNeighbor.GetEnhCslTxAttempts(), + aNeighbor.GetEnhCslMaxTxAttempts()); + + if (aNeighbor.GetEnhCslTxAttempts() >= aNeighbor.GetEnhCslMaxTxAttempts()) + { + // CSL transmission attempts reach max, consider neighbor out of sync + aNeighbor.SetEnhCslSynchronized(false); + aNeighbor.ResetEnhCslTxAttempts(); + + if (aNeighbor.GetIndirectMessage()->GetType() == Message::kTypeIp6) + { + Get().mIpCounters.mTxFailure++; + } + + Get().RemoveMessageIfNoPendingTx(*aNeighbor.GetIndirectMessage()); + Get().BecomeDetached(); + ExitNow(); + } + + 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()) + { + aNeighbor.SetIndirectDataSequenceNumber(aFrame.GetSequence()); + + if (aFrame.GetSecurityEnabled() && aFrame.IsHeaderUpdated()) + { + uint32_t frameCounter; + uint8_t keyId; + + IgnoreError(aFrame.GetFrameCounter(frameCounter)); + aNeighbor.SetIndirectFrameCounter(frameCounter); + + IgnoreError(aFrame.GetKeyId(keyId)); + aNeighbor.SetIndirectKeyId(keyId); + } + } + + RescheduleCslTx(); + ExitNow(); + + default: + OT_ASSERT(false); + OT_UNREACHABLE_CODE(break); + } + + HandleSentFrameToNeighbor(aFrame, mFrameContext, kErrorNone, aNeighbor); + +exit: + return; +} + +void EnhCslSender::HandleSentFrameToNeighbor(const Mac::TxFrame &aFrame, + const FrameContext &aContext, + otError aError, + Neighbor &aNeighbor) +{ + Message *message = aNeighbor.GetIndirectMessage(); + uint16_t nextOffset = aContext.mMessageNextOffset; + + if ((message != nullptr) && (nextOffset < message->GetLength())) + { + aNeighbor.SetIndirectFragmentOffset(nextOffset); + RescheduleCslTx(); + ExitNow(); + } + + if (message != nullptr) + { + // The indirect tx of this message to the neighbor is done. + + Mac::Address macDest; + + aNeighbor.SetIndirectMessage(nullptr); + aNeighbor.GetLinkInfo().AddMessageTxStatus(true); + OT_ASSERT(aNeighbor.GetIndirectMessageCount() > 0); + aNeighbor.DecrementIndirectMessageCount(); + + if (!aFrame.IsEmpty()) + { + IgnoreError(aFrame.GetDstAddr(macDest)); + Get().LogMessage(MeshForwarder::kMessageTransmit, *message, aError, &macDest); + } + + if (message->GetType() == Message::kTypeIp6) + { + (aError == kErrorNone) ? Get().mIpCounters.mTxSuccess++ + : Get().mIpCounters.mTxFailure++; + } + + Get().RemoveMessageIfNoPendingTx(*message); + } + + RescheduleCslTx(); + +exit: + return; +} + +} // namespace ot + +#endif // OPENTHREAD_FTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE diff --git a/src/core/thread/enh_csl_sender.hpp b/src/core/thread/enh_csl_sender.hpp new file mode 100644 index 00000000000..dc6d80e1663 --- /dev/null +++ b/src/core/thread/enh_csl_sender.hpp @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2024, 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. + */ + +#ifndef ENH_CSL_SENDER_HPP_ +#define ENH_CSL_SENDER_HPP_ + +#include "openthread-core-config.h" + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + +#include "common/locator.hpp" +#include "common/message.hpp" +#include "common/non_copyable.hpp" +#include "common/time.hpp" +#include "config/mac.h" +#include "mac/mac.hpp" +#include "mac/mac_frame.hpp" +#include "thread/indirect_sender_frame_context.hpp" + +namespace ot { + +/** + * @addtogroup core-mesh-forwarding + * + * @brief + * Includes definitions for Enhanced CSL transmission. + * @{ + */ + +class Neighbor; + +/** + * Implements enhanced CSL tx functionality. + */ +class EnhCslSender : public InstanceLocator, private NonCopyable +{ + friend class Mac::Mac; + +public: + static constexpr uint8_t kMaxEnhCslTriggeredTxAttempts = OPENTHREAD_CONFIG_MAC_ENH_CSL_TX_ATTEMPTS; + + /** + * Defines all the peer info required for scheduling enhanced CSL transmissions. + * + * `Neighbor` class publicly inherits from this class. + */ + class EnhCslPeerInfo + { + public: + uint8_t GetEnhCslTxAttempts(void) const { return mCslTxAttempts; } + void IncrementEnhCslTxAttempts(void) { mCslTxAttempts++; } + void ResetEnhCslTxAttempts(void) { mCslTxAttempts = 0; } + + uint8_t GetIndirectDataSequenceNumber(void) const { return mIndirectDsn; } + void SetIndirectDataSequenceNumber(uint8_t aDsn) { mIndirectDsn = aDsn; } + + bool IsEnhCslSynchronized(void) const { return mCslSynchronized && mCslPeriod > 0; } + void SetEnhCslSynchronized(bool aCslSynchronized) { mCslSynchronized = aCslSynchronized; } + + bool IsEnhCslPrevSnValid(void) const { return mCslPrevSnValid; } + void SetEnhCslPrevSnValid(bool aCslPrevSnValid) { mCslPrevSnValid = aCslPrevSnValid; } + + uint8_t GetEnhCslPrevSn(void) const { return mCslPrevSn; } + void SetEnhCslPrevSn(uint8_t aCslPrevSn) { mCslPrevSn = aCslPrevSn; } + + uint16_t GetEnhCslPeriod(void) const { return mCslPeriod; } + void SetEnhCslPeriod(uint16_t aPeriod) { mCslPeriod = aPeriod; } + + uint16_t GetEnhCslPhase(void) const { return mCslPhase; } + void SetEnhCslPhase(uint16_t aPhase) { mCslPhase = aPhase; } + + TimeMilli GetEnhCslLastHeard(void) const { return mCslLastHeard; } + void SetEnhCslLastHeard(TimeMilli aCslLastHeard) { mCslLastHeard = aCslLastHeard; } + + uint64_t GetEnhLastRxTimestamp(void) const { return mLastRxTimestamp; } + void SetEnhLastRxTimestamp(uint64_t aLastRxTimestamp) { mLastRxTimestamp = aLastRxTimestamp; } + + uint32_t GetIndirectFrameCounter(void) const { return mIndirectFrameCounter; } + void SetIndirectFrameCounter(uint32_t aFrameCounter) { mIndirectFrameCounter = aFrameCounter; } + + uint8_t GetIndirectKeyId(void) const { return mIndirectKeyId; } + void SetIndirectKeyId(uint8_t aKeyId) { mIndirectKeyId = aKeyId; } + + Message *GetIndirectMessage(void) { return mIndirectMessage; } + void SetIndirectMessage(Message *aMessage) { mIndirectMessage = aMessage; } + + uint16_t GetIndirectMessageCount(void) const { return mQueuedMessageCount; } + void IncrementIndirectMessageCount(void) { mQueuedMessageCount++; } + void DecrementIndirectMessageCount(void) { mQueuedMessageCount--; } + void ResetIndirectMessageCount(void) { mQueuedMessageCount = 0; } + + uint16_t GetIndirectFragmentOffset(void) const { return mIndirectFragmentOffset; } + void SetIndirectFragmentOffset(uint16_t aFragmentOffset) { mIndirectFragmentOffset = aFragmentOffset; } + + uint8_t GetEnhCslMaxTxAttempts(void) const + { + return mCslMaxTxAttempts != 0 ? mCslMaxTxAttempts : EnhCslSender::kMaxEnhCslTriggeredTxAttempts; + } + void SetEnhCslMaxTxAttempts(uint8_t txAttempts) { mCslMaxTxAttempts = txAttempts; } + void ResetEnhCslMaxTxAttempts() { mCslMaxTxAttempts = 0; } + + private: + uint8_t mCslTxAttempts : 6; ///< Number of enhanced CSL triggered tx attempts. + bool mCslSynchronized : 1; ///< Indicates whether or not the peer is enhanced CSL synchronized. + bool mCslPrevSnValid : 1; ///< Indicates whether or not the previous MAC frame sequence number was set. + uint8_t mCslMaxTxAttempts; ///< Override for the maximum number of enhanced CSL triggered tx attempts. + uint16_t mCslPeriod; ///< Enhanced CSL sampled listening period in units of 10 symbols (160 microseconds). + uint16_t mCslPhase; ///< The time when the next CSL sample will start. + TimeMilli mCslLastHeard; ///< Time when last frame containing CSL IE was heard. + uint64_t mLastRxTimestamp; ///< Time when last frame containing CSL IE was received, in microseconds. + + uint8_t mCslPrevSn; ///< The previous MAC frame sequence number (for MAC-level frame deduplication). + uint8_t mIndirectDsn; // MAC level Data Sequence Number (DSN) for retx attempts. + uint8_t mIndirectKeyId; // Key Id for current indirect frame (used for retx). + uint32_t mIndirectFrameCounter; // Frame counter for current indirect frame (used for retx). + + Message *mIndirectMessage; // Current indirect message. + uint16_t mQueuedMessageCount; // Number of queued indirect messages for the peer. + uint16_t mIndirectFragmentOffset; // 6LoWPAN fragment offset for the indirect message. + }; + + /** + * This constructor initializes the enhanced CSL sender object. + * + * @param[in] aInstance A reference to the OpenThread instance. + */ + explicit EnhCslSender(Instance &aInstance); + + /** + * Adds a message for enhanced CSL transmission to a neighbor. + * + * @param[in] aMessage The message to add. + * @param[in] aNeighbor The neighbor for enhanced CSL transmission. + */ + void AddMessageForCslPeer(Message &aMessage, Neighbor &aNeighbor); + + /** + * Removes all added messages for a specific neighbor. + * + * @param[in] aNeighbor The neighbor for enhanced CSL transmission. + */ + void ClearAllMessagesForCslPeer(Neighbor &aNeighbor); + + /** + * Updates the next CSL transmission (finds the nearest neighbor). + * + * It would then request the `Mac` to do the CSL tx. If the last CSL tx has been fired at `Mac` but hasn't been + * done yet, and it's aborted, this method would set `mCslTxNeighbor` to `nullptr` to notify the + * `HandleTransmitDone` that the operation has been aborted. + */ + void Update(void); + + /** + * Returns the current parent or parent candidate. + * + * @returns The current parent or parent candidate. + */ + Neighbor *GetParent(void) const; + +private: + // Guard time in usec to add when checking delay while preparaing the CSL frame for tx. + static constexpr uint32_t kFramePreparationGuardInterval = 1500; + + typedef IndirectSenderBase::FrameContext FrameContext; + + void InitFrameRequestAhead(void); + void RescheduleCslTx(void); + uint32_t GetNextCslTransmissionDelay(Neighbor &aNeighbor, uint32_t &aDelayFromLastRx, uint32_t aAheadUs) const; + Error PrepareFrameForNeighbor(Mac::TxFrame &aFrame, FrameContext &aContext, Neighbor &aNeighbor); + uint16_t PrepareDataFrame(Mac::TxFrame &aFrame, Neighbor &aNeighbor, Message &aMessage); + void HandleSentFrameToNeighbor(const Mac::TxFrame &aFrame, + const FrameContext &aContext, + otError aError, + Neighbor &aNeighbor); + + // Callbacks from `Mac` + Mac::TxFrame *HandleFrameRequest(Mac::TxFrames &aTxFrames); + void HandleSentFrame(const Mac::TxFrame &aFrame, Error aError); + + void HandleSentFrame(const Mac::TxFrame &aFrame, Error aError, Neighbor &aNeighbor); + + Neighbor *mCslTxNeigh; + Message *mCslTxMessage; + uint32_t mCslFrameRequestAheadUs; + FrameContext mFrameContext; +}; + +/** + * @} + * + */ + +} // namespace ot + +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + +#endif // ENH_CSL_SENDER_HPP_ diff --git a/src/core/thread/indirect_sender_frame_context.hpp b/src/core/thread/indirect_sender_frame_context.hpp index e0e7e098c4d..8b8277e01f2 100644 --- a/src/core/thread/indirect_sender_frame_context.hpp +++ b/src/core/thread/indirect_sender_frame_context.hpp @@ -72,6 +72,9 @@ class IndirectSenderBase { friend class IndirectSender; friend class CslTxScheduler; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + friend class EnhCslSender; +#endif private: uint16_t mMessageNextOffset; ///< The next offset into the message associated with the prepared frame. diff --git a/src/core/thread/mesh_forwarder.cpp b/src/core/thread/mesh_forwarder.cpp index d2395d3e7e9..1e1c2fb5d57 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -108,6 +108,9 @@ MeshForwarder::MeshForwarder(Instance &aInstance) , mIndirectSender(aInstance) #endif , mDataPollSender(aInstance) +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + , mEnhCslSender(aInstance) +#endif { mFragTag = Random::NonCrypto::GetUint16(); @@ -685,14 +688,27 @@ void MeshForwarder::SetRxOnWhenIdle(bool aRxOnWhenIdle) { Get().SetRxOnWhenIdle(aRxOnWhenIdle); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + // Data polls not allowed in enhanced CSL mode + if (!Get().IsWakeupCoordPresent()) +#endif + { + if (aRxOnWhenIdle) + { + mDataPollSender.StopPolling(); + } + else + { + mDataPollSender.StartPolling(); + } + } + if (aRxOnWhenIdle) { - mDataPollSender.StopPolling(); Get().Stop(); } else { - mDataPollSender.StartPolling(); Get().Start(); } } @@ -1082,7 +1098,12 @@ uint16_t MeshForwarder::PrepareDataFrame(Mac::TxFrame &aFrame, if (nextOffset < aMessage.GetLength()) { - aFrame.SetFramePending(true); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (!Get().IsWakeupCoordPresent()) +#endif + { + aFrame.SetFramePending(true); + } #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE aMessage.SetTimeSync(false); #endif diff --git a/src/core/thread/mesh_forwarder.hpp b/src/core/thread/mesh_forwarder.hpp index 1714bba1ad6..1d89d6dca9a 100644 --- a/src/core/thread/mesh_forwarder.hpp +++ b/src/core/thread/mesh_forwarder.hpp @@ -52,6 +52,7 @@ #include "net/ip6.hpp" #include "thread/address_resolver.hpp" #include "thread/child.hpp" +#include "thread/enh_csl_sender.hpp" #include "thread/indirect_sender.hpp" #include "thread/lowpan.hpp" #include "thread/network_data_leader.hpp" @@ -161,6 +162,9 @@ class MeshForwarder : public InstanceLocator, private NonCopyable friend class Ip6::Ip6; friend class Mle::DiscoverScanner; friend class TimeTicker; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + friend class EnhCslSender; +#endif public: /** @@ -650,6 +654,10 @@ class MeshForwarder : public InstanceLocator, private NonCopyable DataPollSender mDataPollSender; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + EnhCslSender mEnhCslSender; +#endif + #if OPENTHREAD_CONFIG_TX_QUEUE_STATISTICS_ENABLE TxQueueStats mTxQueueStats; #endif diff --git a/src/core/thread/mesh_forwarder_mtd.cpp b/src/core/thread/mesh_forwarder_mtd.cpp index 9dec26b43b8..73273a48504 100644 --- a/src/core/thread/mesh_forwarder_mtd.cpp +++ b/src/core/thread/mesh_forwarder_mtd.cpp @@ -32,6 +32,7 @@ */ #include "mesh_forwarder.hpp" +#include "instance/instance.hpp" #if OPENTHREAD_MTD @@ -41,10 +42,22 @@ void MeshForwarder::SendMessage(OwnedPtr aMessagePtr) { Message &message = *aMessagePtr.Release(); - message.SetDirectTransmission(); - message.SetOffset(0); - message.SetDatagramTag(0); - message.SetTimestampToNow(); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + Neighbor *neighbor = Get().GetParent(); + + if ((neighbor != nullptr) && neighbor->IsEnhCslSynchronized()) + { + // Destined for an enhanced CSL peer + mEnhCslSender.AddMessageForCslPeer(message, *neighbor); + } + else +#endif + { + message.SetDirectTransmission(); + message.SetOffset(0); + message.SetDatagramTag(0); + message.SetTimestampToNow(); + } mSendQueue.Enqueue(message); mScheduleTransmissionTask.Post(); diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 8d1b19b909a..d7da0bc5006 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -173,8 +173,7 @@ Error Mle::Start(StartMode aMode) Get().SubscribeMulticast(mRealmLocalAllThreadNodes); SetRloc16(GetRloc16()); - - mAttachCounter = 0; + ResetAttachCounter(); Get().Start(); @@ -338,6 +337,45 @@ void Mle::SetAttachState(AttachState aState) return; } +void Mle::ResetAttachCounter(void) +{ +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + VerifyOrExit(!IsWakeupCoordPresent()); + +#if OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE + mAttachFireTime = TimeMilli(0); +#endif +#endif + + mAttachCounter = 0; + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +exit: + return; +#endif +} + +void Mle::IncrementAttachCounter(void) +{ +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + VerifyOrExit(!IsWakeupCoordPresent()); +#endif + + mAttachCounter++; + + if (mAttachCounter == 0) + { + mAttachCounter--; + } + + mCounters.mAttachAttempts++; + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +exit: + return; +#endif +} + void Mle::Restore(void) { Settings::NetworkInfo networkInfo; @@ -560,7 +598,7 @@ void Mle::Attach(AttachMode aMode) if (!IsDetached()) { - mAttachCounter = 0; + ResetAttachCounter(); } if (mReattachState == kReattachStart) @@ -839,7 +877,7 @@ Error Mle::SetDeviceMode(DeviceMode aDeviceMode) if (shouldReattach) { - mAttachCounter = 0; + ResetAttachCounter(); IgnoreError(BecomeDetached()); ExitNow(); } @@ -847,7 +885,7 @@ Error Mle::SetDeviceMode(DeviceMode aDeviceMode) if (IsDetached()) { - mAttachCounter = 0; + ResetAttachCounter(); SetStateDetached(); Attach(kAnyPartition); } @@ -1062,6 +1100,12 @@ void Mle::InitNeighbor(Neighbor &aNeighbor, const RxInfo &aRxInfo) aNeighbor.SetLastHeard(TimerMilli::GetNow()); } +void Mle::InitParentCandidate(Mac::ExtAddress &aAddress) +{ + mParentCandidate.Clear(); + mParentCandidate.SetExtAddress(aAddress); +} + void Mle::ScheduleChildUpdateRequestIfMtdChild(void) { if (IsChild() && !IsFullThreadDevice()) @@ -1280,7 +1324,7 @@ void Mle::UpdateServiceAlocs(void) #endif // OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE -Error Mle::DetermineParentRequestType(ParentRequestType &aType) const +Error Mle::DetermineParentRequestType(ParentRequestType &aType, uint32_t *aTimeout) const { // This method determines the Parent Request type to use during an // attach cycle based on `mAttachMode`, `mAttachCounter` and @@ -1297,6 +1341,28 @@ Error Mle::DetermineParentRequestType(ParentRequestType &aType) const OT_ASSERT(mAttachState == kAttachStateParentRequest); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (IsWakeupCoordPresent()) + { + aType = kToWakeupCoord; + + if (aTimeout != nullptr) + { + TimeMilli now = TimerMilli::GetNow(); + TimeMilli windowEnd = mWakeupCoordAttachTime + mWakeupCoordAttachWindow; + + VerifyOrExit(now < windowEnd, error = kErrorNotFound); + // Let the connection window only limit the duration of Parent Request transmissions, + // but wait for Parent Response a bit longer than that. + *aTimeout = (windowEnd - now) + kWakeupCoordParentRespTimeout; + } + + ExitNow(); + } +#else + OT_UNUSED_VARIABLE(aTimeout); +#endif + aType = kToRoutersAndReeds; // If device is not yet attached, `mAttachCounter` will track the @@ -1414,7 +1480,7 @@ void Mle::HandleAttachTimer(void) switch (mAttachState) { case kAttachStateIdle: - mAttachCounter = 0; + ResetAttachCounter(); break; case kAttachStateProcessAnnounce: @@ -1422,8 +1488,17 @@ void Mle::HandleAttachTimer(void) break; case kAttachStateStart: - LogNote("Attach attempt %d, %s %s", mAttachCounter, AttachModeToString(mAttachMode), - ReattachStateToString(mReattachState)); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (IsWakeupCoordPresent()) + { + LogNote("Attach attempt to Wake-up Coordinator"); + } + else +#endif + { + LogNote("Attach attempt %d, %s %s", mAttachCounter, AttachModeToString(mAttachMode), + ReattachStateToString(mReattachState)); + } SetAttachState(kAttachStateParentRequest); mParentCandidate.SetState(Neighbor::kStateInvalid); @@ -1435,10 +1510,9 @@ void Mle::HandleAttachTimer(void) case kAttachStateParentRequest: mParentRequestCounter++; - if (DetermineParentRequestType(type) == kErrorNone) + if (DetermineParentRequestType(type, &delay) == kErrorNone) { SendParentRequest(type); - delay = (type == kToRouters) ? kParentRequestRouterTimeout : kParentRequestReedTimeout; break; } @@ -1493,6 +1567,10 @@ bool Mle::PrepareAnnounceState(void) bool shouldAnnounce = false; Mac::ChannelMask channelMask; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + VerifyOrExit(!IsWakeupCoordPresent()); +#endif + VerifyOrExit(!IsChild() && (mReattachState == kReattachStop) && (Get().IsPartiallyComplete() || !IsFullThreadDevice())); @@ -1594,11 +1672,20 @@ void Mle::SendParentRequest(ParentRequestType aType) { case kToRouters: scanMask = ScanMaskTlv::kRouterFlag; + destination.SetToLinkLocalAllRoutersMulticast(); break; case kToRoutersAndReeds: scanMask = ScanMaskTlv::kRouterFlag | ScanMaskTlv::kEndDeviceFlag; + destination.SetToLinkLocalAllRoutersMulticast(); + break; + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + case kToWakeupCoord: + scanMask = ScanMaskTlv::kRouterFlag | ScanMaskTlv::kEndDeviceFlag; + destination.SetToLinkLocalAddress(mWakeupCoord); break; +#endif } VerifyOrExit((message = NewMleMessage(kCommandParentRequest)) != nullptr, error = kErrorNoBufs); @@ -1609,8 +1696,12 @@ void Mle::SendParentRequest(ParentRequestType aType) #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE SuccessOrExit(error = message->AppendTimeRequestTlv()); #endif - - destination.SetToLinkLocalAllRoutersMulticast(); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (aType == kToWakeupCoord) + { + SuccessOrExit(error = message->AppendCslClockAccuracyTlv()); + } +#endif SuccessOrExit(error = message->SendTo(destination)); switch (aType) @@ -1622,6 +1713,13 @@ void Mle::SendParentRequest(ParentRequestType aType) case kToRoutersAndReeds: Log(kMessageSend, kTypeParentRequestToRoutersReeds, destination); break; + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + case kToWakeupCoord: + Log(kMessageSend, kTypeParentRequestToWakeupCoord, destination); + LogInfo("Sent Parent Request FC: %lu", ToUlong(Get().GetMleFrameCounter() - 1)); + break; +#endif } exit: @@ -2094,6 +2192,9 @@ void Mle::SendAnnounce(uint8_t aChannel, const Ip6::Address &aDestination, Annou MeshCoP::Timestamp activeTimestamp; TxMessage *message = nullptr; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + VerifyOrExit(!IsWakeupCoordPresent(), error = kErrorInvalidState); +#endif VerifyOrExit(Get().GetSupportedChannelMask().ContainsChannel(aChannel), error = kErrorInvalidArgs); VerifyOrExit((message = NewMleMessage(kCommandAnnounce)) != nullptr, error = kErrorNoBufs); message->SetLinkSecurityEnabled(true); @@ -3643,7 +3744,7 @@ void Mle::HandleAnnounce(RxInfo &aRxInfo) mAlternatePanId = panId; SetAttachState(kAttachStateProcessAnnounce); mAttachTimer.Start(kAnnounceProcessTimeout); - mAttachCounter = 0; + ResetAttachCounter(); LogNote("Delay processing Announce - channel %d, panid 0x%02x", channel, panId); } @@ -3802,6 +3903,9 @@ void Mle::InformPreviousParent(void) Message *message = nullptr; Ip6::MessageInfo messageInfo; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + VerifyOrExit(!IsWakeupCoordPresent()); +#endif VerifyOrExit((message = Get().NewMessage(0)) != nullptr, error = kErrorNoBufs); SuccessOrExit(error = message->SetLength(0)); @@ -4023,6 +4127,9 @@ const char *Mle::MessageTypeToString(MessageType aType) #endif #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE "Time Sync", // (31) kTypeTimeSync +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + "Parent Request", // kTypeParentRequestToWakeupCoord #endif }; @@ -5150,6 +5257,32 @@ Error Mle::RxMessage::ReadRouteTlv(RouteTlv &aRouteTlv) const } #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +void Mle::AttachToWakeupCoord(const Mac::ExtAddress &aCoord, TimeMilli aAttachTime, uint32_t aAttachWindowMs) +{ +#if OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE + if (mAttachTimer.IsRunning()) + { + mAttachFireTime = mAttachTimer.GetFireTime(); + } +#endif + + mPreviousRole = mRole; + SetStateDetached(); + mParent.Clear(); + SetRloc16(Mac::kShortAddrInvalid); + // Clear the current network data to avoid needless address registrations as the Wake-up Coordinator acts as a + // leader and establishes new network data, anyway. + Get().Reset(); + + mWakeupCoord = aCoord; + mWakeupCoordAttachTime = aAttachTime; + mWakeupCoordAttachWindow = aAttachWindowMs; + + Attach(kAnyPartition); +} +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + //--------------------------------------------------------------------------------------------------------------------- // ParentCandidate diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 6dcc552dee7..26a9ab4d419 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -116,6 +116,10 @@ class Mle : public InstanceLocator, private NonCopyable friend class ot::LinkMetrics::Initiator; #endif friend class ot::UnitTester; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + friend class ot::EnhCslSender; + friend class ot::Mac::Mac; +#endif public: typedef otDetachGracefullyCallback DetachCallback; ///< Callback to signal end of graceful detach. @@ -718,6 +722,27 @@ class Mle : public InstanceLocator, private NonCopyable #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + /** + * Returns whether the Thread interface is currently communicating to a Wake-up Coordinator. + * + * @retval TRUE If the Thread interface is communicating to a Wake-up Coordinator. + * @retval FALSE If the Thread interface is not communicating to a Wake-up Coordinator. + */ + bool IsWakeupCoordPresent() const { return mWakeupCoordAttachWindow > 0; } + + /** + * Attaches to a Wake-up Coordinator. + * + * This detaches from the current parent and initiates attachment to the Wake-up Coordinator. + * + * @param[in] aCoord The extended address of the Wake-up Coordinator. + * @param[in] aAttachTime The time when Parent Requests start being sent to the Wake-up Coordinator. + * @param[in] aAttachWindowMs The connection window for receiving the Parent Response. + */ + void AttachToWakeupCoord(const Mac::ExtAddress &aCoord, TimeMilli aAttachTime, uint32_t aAttachWindowMs); +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + private: //------------------------------------------------------------------------------------------------------------------ // Constants @@ -849,6 +874,9 @@ class Mle : public InstanceLocator, private NonCopyable { kToRouters, // Parent Request to routers only. kToRoutersAndReeds, // Parent Request to all routers and REEDs. +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + kToWakeupCoord, // Parent Request unicast to a known Wake-up Coordinator device. +#endif }; enum ChildUpdateRequestState : uint8_t @@ -924,6 +952,9 @@ class Mle : public InstanceLocator, private NonCopyable #endif #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE kTypeTimeSync, +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + kTypeParentRequestToWakeupCoord, #endif }; @@ -1062,6 +1093,26 @@ class Mle : public InstanceLocator, private NonCopyable Class mClass; // The message class (authoritative, peer, or unknown). }; + /* + * Resets the attach counter. + * + */ + void ResetAttachCounter(void); + + /** + * Increments the attach counter. + * + */ + void IncrementAttachCounter(void); + + /* + * Initialize parent candidate. + * + * @param[in] aAddress The MAC Address of the parent candidate. + * + */ + void InitParentCandidate(Mac::ExtAddress &aAddress); + //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void HandleDelayedSenderTimer(void) { mDelayedSender.HandleTimer(); } @@ -1255,7 +1306,7 @@ class Mle : public InstanceLocator, private NonCopyable void SendAnnounce(uint8_t aChannel, const Ip6::Address &aDestination, AnnounceMode aMode = kNormalAnnounce); uint32_t Reattach(void); bool HasAcceptableParentCandidate(void) const; - Error DetermineParentRequestType(ParentRequestType &aType) const; + Error DetermineParentRequestType(ParentRequestType &aType, uint32_t *aTimeout = nullptr) const; bool IsBetterParent(uint16_t aRloc16, uint8_t aTwoWayLinkMargin, const ConnectivityTlv &aConnectivityTlv, @@ -1404,6 +1455,15 @@ class Mle : public InstanceLocator, private NonCopyable Ip6::Netif::UnicastAddress mMeshLocalRloc; Ip6::Netif::MulticastAddress mLinkLocalAllThreadNodes; Ip6::Netif::MulticastAddress mRealmLocalAllThreadNodes; +#if OPENTHREAD_CONFIG_MLE_ATTACH_BACKOFF_ENABLE + TimeMilli mAttachFireTime; +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + DeviceRole mPreviousRole; + Mac::ExtAddress mWakeupCoord; + TimeMilli mWakeupCoordAttachTime; + uint32_t mWakeupCoordAttachWindow; +#endif }; } // namespace Mle diff --git a/src/core/thread/mle_types.hpp b/src/core/thread/mle_types.hpp index 661c19b0141..8a84fb9b228 100644 --- a/src/core/thread/mle_types.hpp +++ b/src/core/thread/mle_types.hpp @@ -75,14 +75,15 @@ namespace Mle { constexpr uint16_t kUdpPort = 19788; ///< MLE UDP Port -constexpr uint16_t kMaxChildren = OPENTHREAD_CONFIG_MLE_MAX_CHILDREN; ///< Maximum number of children -constexpr uint16_t kMinChildId = 1; ///< Minimum Child ID -constexpr uint16_t kMaxChildId = 511; ///< Maximum Child ID -constexpr uint8_t kMaxRouters = OPENTHREAD_CONFIG_MLE_MAX_ROUTERS; ///< Maximum number of routers -constexpr uint8_t kMaxRouterId = OT_NETWORK_MAX_ROUTER_ID; ///< Max Router ID -constexpr uint8_t kInvalidRouterId = kMaxRouterId + 1; ///< Value indicating invalid Router ID -constexpr uint8_t kRouterIdOffset = 10; ///< Bit offset of router ID in RLOC16 -constexpr uint16_t kInvalidRloc16 = Mac::kShortAddrInvalid; ///< Invalid RLOC16. +constexpr uint16_t kMaxChildren = OPENTHREAD_CONFIG_MLE_MAX_CHILDREN; ///< Maximum number of children +constexpr uint32_t kWakeupCoordParentRespTimeout = 500; ///< Max delay for receiving a Parent Response from WC (ms) +constexpr uint16_t kMinChildId = 1; ///< Minimum Child ID +constexpr uint16_t kMaxChildId = 511; ///< Maximum Child ID +constexpr uint8_t kMaxRouters = OPENTHREAD_CONFIG_MLE_MAX_ROUTERS; ///< Maximum number of routers +constexpr uint8_t kMaxRouterId = OT_NETWORK_MAX_ROUTER_ID; ///< Max Router ID +constexpr uint8_t kInvalidRouterId = kMaxRouterId + 1; ///< Value indicating invalid Router ID +constexpr uint8_t kRouterIdOffset = 10; ///< Bit offset of router ID in RLOC16 +constexpr uint16_t kInvalidRloc16 = Mac::kShortAddrInvalid; ///< Invalid RLOC16. #if OPENTHREAD_CONFIG_MLE_LONG_ROUTES_ENABLE constexpr uint8_t kMaxRouteCost = 127; ///< Maximum path cost diff --git a/src/core/thread/neighbor.hpp b/src/core/thread/neighbor.hpp index ad03df4307f..1eb3b34fd30 100644 --- a/src/core/thread/neighbor.hpp +++ b/src/core/thread/neighbor.hpp @@ -53,6 +53,7 @@ #include "radio/radio.hpp" #include "radio/trel_link.hpp" #include "thread/csl_tx_scheduler.hpp" +#include "thread/enh_csl_sender.hpp" #include "thread/indirect_sender.hpp" #include "thread/link_metrics_types.hpp" #include "thread/link_quality.hpp" @@ -76,6 +77,10 @@ class Neighbor : public InstanceLocatorInit , public Trel::NeighborInfo #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + , + public EnhCslSender::EnhCslPeerInfo +#endif { public: /**