From 91474ad3da0cdaebe55ccce876be73ab2812307b Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 4 Aug 2026 23:27:20 -0700 Subject: [PATCH] [sub-mac] generalize timed transmission handling and states (#13460) This commit simplifies and generalizes target TX time handling in `SubMac`: - Encapsulates `TxDelay` and `TxDelayBaseTime` in `TxFrameProperties` behind `IsTargetTxTimeSpecified()`, `GetTargetTxTime()`, `ClearTargetTxTime()`, and `SetTargetTxTime()`, moving low-level delay accessors to private scope. - Introduces derived config `OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE` to generalize timed TX handling across CSL transmitter and Wakeup (Thread Direct). - Renames `SubMac` state `kStateCslTransmit` to `kStateTimedTransmit` and replaces `kCslTransmitTimeAhead` with `kTimedTxLeadTime`. - Updates `CslTxScheduler` and `WakeupTxScheduler` to use new methods like `SetTargetTxTime()`. --- src/core/mac/mac_links.hpp | 3 +- src/core/mac/sub_mac.cpp | 59 ++++++++++++---------------- src/core/mac/sub_mac.hpp | 35 ++++++++++++----- src/core/mac/wakeup_tx_scheduler.cpp | 7 ++-- src/core/radio/radio_frame.hpp | 39 ++++++++++++------ src/core/radio/radio_types.hpp | 3 +- src/core/thread/csl_tx_scheduler.cpp | 3 +- 7 files changed, 85 insertions(+), 64 deletions(-) diff --git a/src/core/mac/mac_links.hpp b/src/core/mac/mac_links.hpp index 6f8d44019..920677f15 100644 --- a/src/core/mac/mac_links.hpp +++ b/src/core/mac/mac_links.hpp @@ -171,8 +171,7 @@ public: mTxFrame802154.SetIsSecurityProcessed(false); mTxFrame802154.SetCsmaCaEnabled(true); // Set to true by default, only set to `false` for CSL transmission mTxFrame802154.SetIsHeaderUpdated(false); - mTxFrame802154.SetTxDelay(0); - mTxFrame802154.SetTxDelayBaseTime(0); + mTxFrame802154.ClearTargetTxTime(); mTxFrame802154.SetTxPower(Radio::kInvalidPower); mTxFrame802154.SetCslIePresent(false); #endif diff --git a/src/core/mac/sub_mac.cpp b/src/core/mac/sub_mac.cpp index 7d0e36826..2668924ae 100644 --- a/src/core/mac/sub_mac.cpp +++ b/src/core/mac/sub_mac.cpp @@ -116,7 +116,7 @@ SubMac::Capabilities SubMac::GetCaps(void) const #endif { caps |= (kCapAckTimeout | kCapCsmaBackoff | kCapTransmitRetries | kCapEnergyScan | kCapTransmitSec); -#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE caps |= kCapTransmitTiming; #endif } @@ -295,8 +295,8 @@ Error SubMac::Send(void) { case kStateDisabled: case kStateCsmaBackoff: -#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE - case kStateCslTransmit: +#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE + case kStateTimedTransmit: #endif case kStateTransmit: #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY @@ -392,24 +392,19 @@ exit: void SubMac::StartCsmaBackoff(void) { -#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE - if (mTransmitFrame.GetTxDelay() != 0 || mTransmitFrame.GetTxDelayBaseTime() != 0) +#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE + if (mTransmitFrame.IsTargetTxTimeSpecified()) { - SetState(kStateCslTransmit); + SetState(kStateTimedTransmit); - if (ShouldHandleTransmitTargetTime()) + if (ShouldHandle(kCapTransmitTiming)) { - static constexpr uint32_t kAheadTime = - kCcaSampleInterval + kCslTransmitTimeAhead + Radio::kHeaderShrDuration; + Radio::Time32 txStart = mTransmitFrame.GetTargetTxTime() - kTimedTxLeadTime; + Radio::Time32 radioNow = Get().GetNowAsTime32(); - Radio::Time32 radioNow = Get().GetNowAsTime32(); - Radio::Time32 txStartTime = mTransmitFrame.GetTxDelayBaseTime(); - - txStartTime += (mTransmitFrame.GetTxDelay() - kAheadTime); - - if (Radio::IsTimeStrictlyBefore(radioNow, txStartTime)) + if (Radio::IsTimeStrictlyBefore(radioNow, txStart)) { - StartTimer(txStartTime - radioNow); + StartTimer(txStart - radioNow); ExitNow(); } @@ -419,7 +414,7 @@ void SubMac::StartCsmaBackoff(void) BeginTransmit(); ExitNow(); } -#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +#endif // OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE SetState(kStateCsmaBackoff); @@ -468,8 +463,8 @@ void SubMac::BeginTransmit(void) { Error error; -#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE - VerifyOrExit(mState == kStateCsmaBackoff || mState == kStateCslTransmit); +#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE + VerifyOrExit(mState == kStateCsmaBackoff || mState == kStateTimedTransmit); #else VerifyOrExit(mState == kStateCsmaBackoff); #endif @@ -483,14 +478,14 @@ void SubMac::BeginTransmit(void) error = Get().Transmit(mTransmitFrame); - if (error == kErrorInvalidState && mTransmitFrame.GetTxDelay() > 0) +#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE + if (error == kErrorInvalidState && mTransmitFrame.IsTargetTxTimeSpecified()) { // Platform `transmit_at` fails and we send the frame directly. - mTransmitFrame.SetTxDelay(0); - mTransmitFrame.SetTxDelayBaseTime(0); - + mTransmitFrame.ClearTargetTxTime(); error = Get().Transmit(mTransmitFrame); } +#endif SuccessOrAssert(error); @@ -714,8 +709,8 @@ Error SubMac::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration) case kStateDisabled: case kStateCsmaBackoff: case kStateTransmit: -#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE - case kStateCslTransmit: +#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE + case kStateTimedTransmit: #endif #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY case kStateDelayBeforeRetx: @@ -796,10 +791,8 @@ void SubMac::HandleTimer(void) { switch (mState) { -#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE - case kStateCslTransmit: - BeginTransmit(); - break; +#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE + case kStateTimedTransmit: #endif case kStateCsmaBackoff: BeginTransmit(); @@ -1078,7 +1071,7 @@ const char *SubMac::StateToString(State aState) _(kStateCsmaBackoff, "CsmaBackoff") \ _(kStateTransmit, "Transmit") \ _(kStateEnergyScan, "EnergyScan") \ - DelayBeforeRetxStateMapList(_) CslTxStateMapList(_) RadioSampleMapList(_) + DelayBeforeRetxStateMapList(_) TimedTxStateMapList(_) RadioSampleMapList(_) #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY #define DelayBeforeRetxStateMapList(_) _(kStateDelayBeforeRetx, "DelayBeforeRetx") @@ -1086,10 +1079,10 @@ const char *SubMac::StateToString(State aState) #define DelayBeforeRetxStateMapList(_) #endif -#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE -#define CslTxStateMapList(_) _(kStateCslTransmit, "CslTransmit") +#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE +#define TimedTxStateMapList(_) _(kStateTimedTransmit, "TimedTransmit") #else -#define CslTxStateMapList(_) +#define TimedTxStateMapList(_) #endif #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE diff --git a/src/core/mac/sub_mac.hpp b/src/core/mac/sub_mac.hpp index a371b1ecf..b728e4ccf 100644 --- a/src/core/mac/sub_mac.hpp +++ b/src/core/mac/sub_mac.hpp @@ -60,6 +60,20 @@ namespace ot { namespace Mac { +//---------------------------------------------------------------------------------------------------------------------- +// Derived configs + +#ifdef OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE +#error "OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE MUST NOT be defined directly. It is derived from other configs" +#endif + +#define OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE \ + (OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE || OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE || \ + ((OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE) && OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE)) + +//---------------------------------------------------------------------------------------------------------------------- +// Config validity checks + #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE && (OPENTHREAD_CONFIG_THREAD_VERSION < OT_THREAD_VERSION_1_2) #error "Thread 1.2 or higher version is required for OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE." #endif @@ -81,6 +95,8 @@ namespace Mac { #error "OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE is required for OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE." #endif +//---------------------------------------------------------------------------------------------------------------------- + #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE class LinkRaw; #endif @@ -519,8 +535,7 @@ private: ConditionalCap(kCapTransmitRetries, OPENTHREAD_CONFIG_MAC_SOFTWARE_RETRANSMIT_ENABLE) | ConditionalCap(kCapCsmaBackoff, OPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE) | ConditionalCap(kCapTransmitSec, OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_SECURITY_ENABLE) | - ConditionalCap(kCapTransmitTiming, - OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE &&OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE) | + ConditionalCap(kCapTransmitTiming, OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE) | ConditionalCap(kCapSleepToTx, OPENTHREAD_RADIO); #undef ConditionalCap @@ -538,8 +553,8 @@ private: #if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY kStateDelayBeforeRetx, // Delay before retx #endif -#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE - kStateCslTransmit, // CSL transmission. +#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE + kStateTimedTransmit, // Timed TX (e.g., for CSL) #endif #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE kStateRadioSample, // Mac layer has requested the SubMac to enter sleep state, but the SubMac is in the periodic @@ -563,11 +578,12 @@ private: static constexpr uint32_t kWedReceiveTimeAfter = OPENTHREAD_CONFIG_WED_RECEIVE_TIME_AFTER; #endif -#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE - // CSL transmitter would schedule delayed transmission `kCslTransmitTimeAhead` earlier - // than expected delayed transmit time. The value is in usec. - // Only for radios not supporting kCapTransmitTiming. - static constexpr uint32_t kCslTransmitTimeAhead = OPENTHREAD_CONFIG_CSL_TRANSMIT_TIME_AHEAD; +#if OT_CONFIG_MAC_TARGET_TIME_TX_ENABLE + // Lead time (in microseconds) to schedule a delayed tx earlier + // than expected target tx time. Only used when radio does not + // itself support `kCapTransmitTiming`. + static constexpr uint32_t kTimedTxLeadTime = + OPENTHREAD_CONFIG_CSL_TRANSMIT_TIME_AHEAD + kCcaSampleInterval + Radio::kHeaderShrDuration; #endif void Init(void); @@ -578,7 +594,6 @@ private: bool ShouldHandleAckTimeout(void) const { return ShouldHandle(kCapAckTimeout); } bool ShouldHandleRetries(void) const { return ShouldHandle(kCapTransmitRetries); } bool ShouldHandleEnergyScan(void) const { return ShouldHandle(kCapEnergyScan); } - bool ShouldHandleTransmitTargetTime(void) const { return ShouldHandle(kCapTransmitTiming); } bool ShouldHandleCsmaBackOff(void) const; void ProcessTransmitSecurity(void); diff --git a/src/core/mac/wakeup_tx_scheduler.cpp b/src/core/mac/wakeup_tx_scheduler.cpp index 1199c7360..5c3af98f6 100644 --- a/src/core/mac/wakeup_tx_scheduler.cpp +++ b/src/core/mac/wakeup_tx_scheduler.cpp @@ -83,7 +83,8 @@ Mac::TxFrame *WakeupTxScheduler::PrepareWakeupFrame(Mac::TxFrames &aTxFrames) Mac::Address source; uint32_t radioTxDelay; uint32_t rendezvousTimeUs; - TimeMicro nowUs = TimerMicro::GetNow(); + TimeMicro nowUs = TimerMicro::GetNow(); + Radio::Time64 radioNow = Get().GetNow(); Mac::ConnectionIe *connectionIe; VerifyOrExit(mIsRunning); @@ -100,8 +101,8 @@ Mac::TxFrame *WakeupTxScheduler::PrepareWakeupFrame(Mac::TxFrames &aTxFrames) VerifyOrExit(frame->GenerateWakeupFrame(Get().GetPanId(), mWakeupRequest, source) == kErrorNone, frame = nullptr); - frame->SetTxDelayBaseTime(Get().GetNowAsTime32()); - frame->SetTxDelay(radioTxDelay); + + frame->SetTargetTxTime(radioNow + radioTxDelay, radioNow); frame->SetCsmaCaEnabled(kWakeupFrameTxCca); frame->SetMaxCsmaBackoffs(0); frame->SetMaxFrameRetries(0); diff --git a/src/core/radio/radio_frame.hpp b/src/core/radio/radio_frame.hpp index 92cc93588..6d38116c0 100644 --- a/src/core/radio/radio_frame.hpp +++ b/src/core/radio/radio_frame.hpp @@ -449,34 +449,47 @@ public: #endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE /** - * Gets the TX delay field for the frame. + * Indicates whether or not a target transmission time is specified for the frame. * - * @returns The delay time for the TX frame in microseconds. + * @retval TRUE If a target transmission time is specified. + * @retval FALSE If no target transmission time is specified. */ - uint32_t GetTxDelay(void) const { return AsFrame().mInfo.mTxInfo.mTxDelay; } + bool IsTargetTxTimeSpecified(void) const { return GetTxDelay() != 0 || GetTxDelayBaseTime() != 0; } /** - * Set TX delay field for the frame. + * Gets the target transmission time for the frame as a `Time32`. * - * @param[in] aTxDelay The delay time for the TX frame. + * @returns The target transmission time. */ - void SetTxDelay(uint32_t aTxDelay) { AsFrame().mInfo.mTxInfo.mTxDelay = aTxDelay; } + Time32 GetTargetTxTime(void) const { return GetTxDelayBaseTime() + GetTxDelay(); } /** - * Gets the TX delay base time field for the frame. - * - * @returns The delay base time for the TX frame as a `Time32`. + * Clears the target transmission time for the frame (sets delay and base time to zero). */ - Time32 GetTxDelayBaseTime(void) const { return AsFrame().mInfo.mTxInfo.mTxDelayBaseTime; } + void ClearTargetTxTime(void) + { + SetTxDelayBaseTime(0); + SetTxDelay(0); + } /** - * Set TX delay base time field for the frame. + * Sets the target transmission time and base reference time for the frame. * - * @param[in] aTxDelayBaseTime The delay base time for the TX frame. + * @param[in] aTargetTxTime The target transmission time. + * @param[in] aBaseTime The base reference time. */ - void SetTxDelayBaseTime(Time32 aTxDelayBaseTime) { AsFrame().mInfo.mTxInfo.mTxDelayBaseTime = aTxDelayBaseTime; } + void SetTargetTxTime(Time64 aTargetTxTime, Time64 aBaseTime) + { + SetTxDelayBaseTime(ConvertTime64To32(aBaseTime)); + SetTxDelay(static_cast(aTargetTxTime - aBaseTime)); + } private: + uint32_t GetTxDelay(void) const { return AsFrame().mInfo.mTxInfo.mTxDelay; } + void SetTxDelay(uint32_t aTxDelay) { AsFrame().mInfo.mTxInfo.mTxDelay = aTxDelay; } + Time32 GetTxDelayBaseTime(void) const { return AsFrame().mInfo.mTxInfo.mTxDelayBaseTime; } + void SetTxDelayBaseTime(Time32 aTxDelayBaseTime) { AsFrame().mInfo.mTxInfo.mTxDelayBaseTime = aTxDelayBaseTime; } + Frame &AsFrame(void) { return *static_cast(this); } const Frame &AsFrame(void) const { return *static_cast(this); } }; diff --git a/src/core/radio/radio_types.hpp b/src/core/radio/radio_types.hpp index f2edf7782..90f0332ba 100644 --- a/src/core/radio/radio_types.hpp +++ b/src/core/radio/radio_types.hpp @@ -52,7 +52,8 @@ namespace Radio { #define OT_CONFIG_RADIO_TIME_ENABLE \ (OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE || \ OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE || OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE || \ - OPENTHREAD_CONFIG_TIME_SYNC_ENABLE) + OPENTHREAD_CONFIG_TIME_SYNC_ENABLE || \ + ((OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE) && OPENTHREAD_CONFIG_MAC_SOFTWARE_TX_TIMING_ENABLE)) class Radio; diff --git a/src/core/thread/csl_tx_scheduler.cpp b/src/core/thread/csl_tx_scheduler.cpp index 04a76c383..2b6ba9ffc 100644 --- a/src/core/thread/csl_tx_scheduler.cpp +++ b/src/core/thread/csl_tx_scheduler.cpp @@ -290,8 +290,7 @@ Mac::TxFrame *CslTxScheduler::HandleFrameRequest(Mac::TxFrames &aTxFrames) ExitNow(); } - frame->SetTxDelayBaseTime(Radio::ConvertTime64To32(mCslTxNeighbor->GetLastRxTimestamp())); - frame->SetTxDelay(static_cast(mNeighborCslWindow - mCslTxNeighbor->GetLastRxTimestamp())); + frame->SetTargetTxTime(mNeighborCslWindow, mCslTxNeighbor->GetLastRxTimestamp()); frame->SetCsmaCaEnabled(true); exit: