From b6cc940634d2c0af4c953d85b524bafbb72abefe Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Tue, 8 Mar 2022 17:58:30 -0800 Subject: [PATCH] [mac] feature to add delay on no-ack error before tx retry (#7346) This commit adds a new feature in `SubMac` to add random delay before frame retransmissions. The delay is added only if the previous tx resulted in `kErrorNoAck`. A backoff algorithm similar to one used for CSMA is used to determine the random delay, i.e., starting with a min backoff exponent (BE) which is incremented each time delay is added up to a max backoff exponent. The backoff interval is derived by selecting a random value from `[0, 2^BE]` range multiplied by `kUnitBackoffPeriod=20` times radio symbol time (20x16=320 usec). The `OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY` is added to enable this behavior (which is enabled by default). This commit also adds other configs for this feature including minimum and maximum backoff exponents (the values are from Thread 1.3 spec recommendations). --- src/core/config/mac.h | 34 +++++++++++++++++ src/core/mac/sub_mac.cpp | 81 ++++++++++++++++++++++++++++++++++------ src/core/mac/sub_mac.hpp | 12 ++++++ src/core/radio/radio.hpp | 1 + 4 files changed, 117 insertions(+), 11 deletions(-) diff --git a/src/core/config/mac.h b/src/core/config/mac.h index e24442589..f4821a0c6 100644 --- a/src/core/config/mac.h +++ b/src/core/config/mac.h @@ -85,6 +85,40 @@ #define OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_INDIRECT 0 #endif +/** + * @def OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + * + * Define as 1 to add random backoff delay in between frame transmission retries when the previous attempt resulted in + * no-ack error. + * + */ +#ifndef OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY +#define OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY 1 +#endif + +/** + * @def OPENTHREAD_CONFIG_MAC_RETX_DELAY_MIN_BACKOFF_EXPONENT + * + * Specifies the minimum backoff exponent to start with when adding random delay in between frame transmission + * retries on no-ack error. It is applicable only when `OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY` + * is enabled. + * + */ +#ifndef OPENTHREAD_CONFIG_MAC_RETX_DELAY_MIN_BACKOFF_EXPONENT +#define OPENTHREAD_CONFIG_MAC_RETX_DELAY_MIN_BACKOFF_EXPONENT 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_MAC_RETX_DELAY_MAX_BACKOFF_EXPONENT + * + * Specifies the maximum backoff exponent when adding random delay in between frame transmission retries on no-ack + * error. It is applicable only when `OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY` is enabled. + * + */ +#ifndef OPENTHREAD_CONFIG_MAC_RETX_DELAY_MAX_BACKOFF_EXPONENT +#define OPENTHREAD_CONFIG_MAC_RETX_DELAY_MAX_BACKOFF_EXPONENT 5 +#endif + /** * @def OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE * diff --git a/src/core/mac/sub_mac.cpp b/src/core/mac/sub_mac.cpp index 73e9ae53a..a09a5378b 100644 --- a/src/core/mac/sub_mac.cpp +++ b/src/core/mac/sub_mac.cpp @@ -78,6 +78,9 @@ void SubMac::Init(void) mRxOnWhenBackoff = true; mEnergyScanMaxRssi = kInvalidRssiValue; mEnergyScanEndTime = Time{0}; +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + mRetxDelayBackOffExponent = kRetxDelayMinBackoffExponent; +#endif #if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE mRadioFilterEnabled = false; @@ -342,6 +345,9 @@ Error SubMac::Send(void) case kStateCslTransmit: #endif case kStateTransmit: +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + case kStateDelayBeforeRetx: +#endif case kStateEnergyScan: ExitNow(error = kErrorInvalidState); OT_UNREACHABLE_CODE(break); @@ -363,8 +369,14 @@ Error SubMac::Send(void) #endif ProcessTransmitSecurity(); + mCsmaBackoffs = 0; mTransmitRetries = 0; + +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + mRetxDelayBackOffExponent = kRetxDelayMinBackoffExponent; +#endif + StartCsmaBackoff(); exit: @@ -414,8 +426,7 @@ exit: void SubMac::StartCsmaBackoff(void) { - uint32_t backoff; - uint32_t backoffExponent = kCsmaMinBe + mTransmitRetries + mCsmaBackoffs; + uint8_t backoffExponent = kCsmaMinBe + mCsmaBackoffs; #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE if (mTransmitFrame.mInfo.mTxInfo.mTxDelay != 0) @@ -444,6 +455,7 @@ void SubMac::StartCsmaBackoff(void) ExitNow(); } #endif // !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + SetState(kStateCsmaBackoff); VerifyOrExit(ShouldHandleCsmaBackOff(), BeginTransmit()); @@ -453,8 +465,18 @@ void SubMac::StartCsmaBackoff(void) backoffExponent = kCsmaMaxBe; } - backoff = Random::NonCrypto::GetUint32InRange(0, static_cast(1UL << backoffExponent)); - backoff *= (kUnitBackoffPeriod * OT_RADIO_SYMBOL_TIME); + StartTimerForBackoff(backoffExponent); + +exit: + return; +} + +void SubMac::StartTimerForBackoff(uint8_t aBackoffExponent) +{ + uint32_t backoff; + + backoff = Random::NonCrypto::GetUint32InRange(0, static_cast(1UL << aBackoffExponent)); + backoff *= (kUnitBackoffPeriod * Radio::kSymbolTime); if (mRxOnWhenBackoff) { @@ -471,8 +493,12 @@ void SubMac::StartCsmaBackoff(void) mTimer.Start(backoff / 1000UL); #endif -exit: - return; +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + if (mState == kStateDelayBeforeRetx) + { + LogDebg("Delaying retx for %u usec (be=%d)", backoff, aBackoffExponent); + } +#endif } void SubMac::BeginTransmit(void) @@ -593,6 +619,17 @@ void SubMac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, Error aErro { mTransmitRetries++; aFrame.SetIsARetransmission(true); + +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + if (aError == kErrorNoAck) + { + SetState(kStateDelayBeforeRetx); + StartTimerForBackoff(mRetxDelayBackOffExponent); + mRetxDelayBackOffExponent = OT_MIN(mRetxDelayBackOffExponent + 1, kRetxDelayMaxBackoffExponent); + ExitNow(); + } +#endif + StartCsmaBackoff(); ExitNow(); } @@ -672,6 +709,9 @@ Error SubMac::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration) case kStateTransmit: #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE case kStateCslTransmit: +#endif +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + case kStateDelayBeforeRetx: #endif case kStateEnergyScan: ExitNow(error = kErrorInvalidState); @@ -769,6 +809,12 @@ void SubMac::HandleTimer(void) HandleTransmitDone(mTransmitFrame, nullptr, kErrorNoAck); break; +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + case kStateDelayBeforeRetx: + StartCsmaBackoff(); + break; +#endif + case kStateEnergyScan: SampleRssi(); break; @@ -968,11 +1014,14 @@ const char *SubMac::StateToString(State aState) "CsmaBackoff", // (3) kStateCsmaBackoff "Transmit", // (4) kStateTransmit "EnergyScan", // (5) kStateEnergyScan +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + "DelayBeforeRetx", // (6) kStateDelayBeforeRetx +#endif #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE - "CslTransmit", // (6) kStateCslTransmit + "CslTransmit", // (7) kStateCslTransmit #endif #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - "CslSample", // (7) kStateCslSample + "CslSample", // (8) kStateCslSample #endif }; @@ -982,15 +1031,25 @@ const char *SubMac::StateToString(State aState) static_assert(kStateCsmaBackoff == 3, "kStateCsmaBackoff value is not correct"); static_assert(kStateTransmit == 4, "kStateTransmit value is not correct"); static_assert(kStateEnergyScan == 5, "kStateEnergyScan value is not correct"); + +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + static_assert(kStateDelayBeforeRetx == 6, "kStateDelayBeforeRetx value is not correct"); #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + static_assert(kStateCslTransmit == 7, "kStateCslTransmit value is not correct"); +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + static_assert(kStateCslSample == 8, "kStateCslSample value is not correct"); +#endif +#elif OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + static_assert(kStateCslSample == 7, "kStateCslSample value is not correct"); +#endif + +#elif !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE static_assert(kStateCslTransmit == 6, "kStateCslTransmit value is not correct"); #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE static_assert(kStateCslSample == 7, "kStateCslSample value is not correct"); #endif -#else -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +#elif OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE static_assert(kStateCslSample == 6, "kStateCslSample value is not correct"); -#endif #endif return kStateStrings[aState]; diff --git a/src/core/mac/sub_mac.hpp b/src/core/mac/sub_mac.hpp index 7155fa13c..4e243e97b 100644 --- a/src/core/mac/sub_mac.hpp +++ b/src/core/mac/sub_mac.hpp @@ -584,6 +584,11 @@ private: static constexpr uint32_t kAckTimeout = 16; // Timeout for waiting on an ACK (in msec). static constexpr uint32_t kCcaSampleInterval = 128; // CCA sample interval, 128 usec. +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + static constexpr uint8_t kRetxDelayMinBackoffExponent = OPENTHREAD_CONFIG_MAC_RETX_DELAY_MIN_BACKOFF_EXPONENT; + static constexpr uint8_t kRetxDelayMaxBackoffExponent = OPENTHREAD_CONFIG_MAC_RETX_DELAY_MAX_BACKOFF_EXPONENT; +#endif + #if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE static constexpr uint32_t kEnergyScanRssiSampleInterval = 128; // RSSI sample interval for energy scan, 128 usec #else @@ -598,6 +603,9 @@ private: kStateCsmaBackoff, // CSMA backoff before transmission. kStateTransmit, // Radio is transmitting. kStateEnergyScan, // Energy scan. +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + kStateDelayBeforeRetx, // Delay before retx +#endif #if !OPENTHREAD_MTD && OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE kStateCslTransmit, // CSL transmission. #endif @@ -650,6 +658,7 @@ private: void ProcessTransmitSecurity(void); void SignalFrameCounterUsed(uint32_t aFrameCounter); void StartCsmaBackoff(void); + void StartTimerForBackoff(uint8_t aBackoffExponent); void BeginTransmit(void); void SampleRssi(void); @@ -689,6 +698,9 @@ private: KeyMaterial mNextKey; uint32_t mFrameCounter; uint8_t mKeyId; +#if OPENTHREAD_CONFIG_MAC_ADD_DELAY_ON_NO_ACK_ERROR_BEFORE_RETRY + uint8_t mRetxDelayBackOffExponent; +#endif #if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE TimerMicro mTimer; #else diff --git a/src/core/radio/radio.hpp b/src/core/radio/radio.hpp index 7947ae086..fd8e56143 100644 --- a/src/core/radio/radio.hpp +++ b/src/core/radio/radio.hpp @@ -81,6 +81,7 @@ class Radio : public InstanceLocator, private NonCopyable friend class Instance; public: + static constexpr uint32_t kSymbolTime = OT_RADIO_SYMBOL_TIME; #if (OPENTHREAD_CONFIG_RADIO_2P4GHZ_OQPSK_SUPPORT && OPENTHREAD_CONFIG_RADIO_915MHZ_OQPSK_SUPPORT) static constexpr uint16_t kNumChannelPages = 2; static constexpr uint32_t kSupportedChannels =