diff --git a/Android.mk b/Android.mk index 8decf8db3..1c2f69d85 100644 --- a/Android.mk +++ b/Android.mk @@ -241,6 +241,7 @@ LOCAL_SRC_FILES := \ src/core/thread/announce_begin_server.cpp \ src/core/thread/announce_sender.cpp \ src/core/thread/child_table.cpp \ + src/core/thread/csl_tx_scheduler.cpp \ src/core/thread/discover_scanner.cpp \ src/core/thread/dua_manager.cpp \ src/core/thread/energy_scan_server.cpp \ diff --git a/examples/platforms/simulation/alarm.c b/examples/platforms/simulation/alarm.c index 4157e5e59..010a02929 100644 --- a/examples/platforms/simulation/alarm.c +++ b/examples/platforms/simulation/alarm.c @@ -310,11 +310,9 @@ uint64_t otPlatTimeGet(void) return platformGetNow(); } -#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE uint16_t otPlatTimeGetXtalAccuracy(void) { return 0; } -#endif #endif // OPENTHREAD_SIMULATION_VIRTUAL_TIME == 0 diff --git a/examples/platforms/simulation/openthread-core-simulation-config.h b/examples/platforms/simulation/openthread-core-simulation-config.h index 9fd31aca5..e8c0c6fbb 100644 --- a/examples/platforms/simulation/openthread-core-simulation-config.h +++ b/examples/platforms/simulation/openthread-core-simulation-config.h @@ -145,6 +145,16 @@ */ #define CLI_COAP_SECURE_USE_COAP_DEFAULT_HANDLER 1 +/** + * @def OPENTHREAD_CONFIG_CSL_SAMPLE_WINDOW + * + * The CSL sample window in units of 10 symbols. + * + */ +#ifndef OPENTHREAD_CONFIG_CSL_SAMPLE_WINDOW +#define OPENTHREAD_CONFIG_CSL_SAMPLE_WINDOW 5 +#endif + /** * @def OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE * diff --git a/examples/platforms/simulation/radio.c b/examples/platforms/simulation/radio.c index 028b910fc..222c8b9b5 100644 --- a/examples/platforms/simulation/radio.c +++ b/examples/platforms/simulation/radio.c @@ -31,6 +31,7 @@ #include #include +#include #include #include #include @@ -113,6 +114,17 @@ static int8_t sCcaEdThresh = -74; static bool sSrcMatchEnabled = false; +#if OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2 +static uint8_t sAckIeData[OT_ACK_IE_MAX_SIZE]; +static uint8_t sAckIeDataLength = 0; +#endif + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +static const uint8_t sCslIeHeader[OT_IE_HEADER_IE_SIZE] = {0x04, 0x0d}; +static uint32_t sCslSampleTime; +static uint32_t sCslPeriod; +#endif + #if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE static bool sRadioCoexEnabled = true; #endif @@ -138,7 +150,7 @@ static void ReverseExtAddress(otExtAddress *aReversed, const otExtAddress *aOrig } } -static bool HasFramePending(const otRadioFrame *aFrame) +static bool hasFramePending(const otRadioFrame *aFrame) { bool rval = false; otMacAddress src; @@ -348,6 +360,17 @@ void platformRadioInit(void) #endif } +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +static uint16_t getCslPhase(void) +{ + uint32_t curTime = otPlatAlarmMicroGetNow(); + uint32_t cslPeriodInUs = sCslPeriod * OT_US_PER_TEN_SYMBOLS; + uint32_t diff = ((sCslSampleTime % cslPeriodInUs) - (curTime % cslPeriodInUs) + cslPeriodInUs) % cslPeriodInUs; + + return (uint16_t)(diff / OT_US_PER_TEN_SYMBOLS); +} +#endif + bool otPlatRadioIsEnabled(otInstance *aInstance) { OT_UNUSED_VARIABLE(aInstance); @@ -630,6 +653,13 @@ void radioSendMessage(otInstance *aInstance) } #endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_TIME_SYNC_ENABLE +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (sCslPeriod > 0) + { + otMacFrameSetCslIe(&sTransmitFrame, (uint16_t)sCslPeriod, getCslPhase()); + } +#endif + sTransmitMessage.mChannel = sTransmitFrame.mChannel; otEXPECT(radioProcessTransmitSecurity(&sTransmitFrame) == OT_ERROR_NONE); @@ -803,7 +833,7 @@ void radioSendAck(void) #else otMacFrameIsDataRequest(&sReceiveFrame) #endif - && HasFramePending(&sReceiveFrame)) + && hasFramePending(&sReceiveFrame)) { sReceiveFrame.mInfo.mRxInfo.mAckedWithFramePending = true; } @@ -812,9 +842,18 @@ void radioSendAck(void) // Use enh-ack for 802.15.4-2015 frames if (otMacFrameIsVersion2015(&sReceiveFrame)) { - otEXPECT(otMacFrameGenerateEnhAck(&sReceiveFrame, sReceiveFrame.mInfo.mRxInfo.mAckedWithFramePending, NULL, 0, - &sAckFrame) == OT_ERROR_NONE); - otEXPECT(radioProcessTransmitSecurity(&sAckFrame) == OT_ERROR_NONE); + otEXPECT(otMacFrameGenerateEnhAck(&sReceiveFrame, sReceiveFrame.mInfo.mRxInfo.mAckedWithFramePending, + sAckIeData, sAckIeDataLength, &sAckFrame) == OT_ERROR_NONE); +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (sCslPeriod > 0) + { + otMacFrameSetCslIe(&sAckFrame, (uint16_t)sCslPeriod, getCslPhase()); + } +#endif + if (otMacFrameIsSecurityEnabled(&sAckFrame)) + { + otEXPECT(radioProcessTransmitSecurity(&sAckFrame) == OT_ERROR_NONE); + } } else #endif @@ -1016,6 +1055,55 @@ exit: } #endif +uint64_t otPlatRadioGetNow(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + return otPlatTimeGet(); +} + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +static otError updateIeData(otInstance *aInstance) + +{ + OT_UNUSED_VARIABLE(aInstance); + + otError error = OT_ERROR_NONE; + uint8_t offset = 0; + + if (sCslPeriod > 0) + { + memcpy(sAckIeData, sCslIeHeader, OT_IE_HEADER_IE_SIZE); + offset += OT_IE_HEADER_IE_SIZE + OT_CSL_IE_SIZE; // reserve space for CSL IE + } + + sAckIeDataLength = offset; + + return error; +} + +otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, const otExtAddress *aExtAddr) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aExtAddr); + + otError error = OT_ERROR_NONE; + + sCslPeriod = aCslPeriod; + + error = updateIeData(aInstance); + + return error; +} + +void otPlatRadioUpdateCslSampleTime(otInstance *aInstance, uint32_t aCslSampleTime) +{ + OT_UNUSED_VARIABLE(aInstance); + + sCslSampleTime = aCslSampleTime; +} +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + void otPlatRadioSetMacKey(otInstance * aInstance, uint8_t aKeyIdMode, uint8_t aKeyId, diff --git a/include/openthread/link.h b/include/openthread/link.h index 4e6f7a9b2..9502c9520 100644 --- a/include/openthread/link.h +++ b/include/openthread/link.h @@ -52,6 +52,7 @@ extern "C" { * @{ * */ +#define OT_US_PER_TEN_SYMBOLS 160 ///< The microseconds per 10 symbols. /** * This structure represents link-specific information for messages received from the Thread radio. @@ -1004,6 +1005,72 @@ bool otLinkIsPromiscuous(otInstance *aInstance); */ otError otLinkSetPromiscuous(otInstance *aInstance, bool aPromiscuous); +/** + * This function gets the CSL channel. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The CSL channel. + * + */ +uint8_t otLinkCslGetChannel(otInstance *aInstance); + +/** + * This function sets the CSL channel. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aChannel The CSL sample channel. + * + * @retval OT_ERROR_NONE Successfully set the CSL parameters. + * @retval OT_ERROR_INVALID_ARGS Invalid @p aChannel. + * + */ +otError otLinkCslSetChannel(otInstance *aInstance, uint8_t aChannel); + +/** + * This function gets the CSL period. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The CSL period in units of 10 symbols. + * + */ +uint16_t otLinkCslGetPeriod(otInstance *aInstance); + +/** + * This function sets the CSL period. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aPeriod The CSL period in units of 10 symbols. + * + * @retval OT_ERROR_NONE Successfully set the CSL period. + * @retval OT_ERROR_INVALID_ARGS Invalid CSL period. + * + */ +otError otLinkCslSetPeriod(otInstance *aInstance, uint16_t aPeriod); + +/** + * This function gets the CSL timeout. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The CSL timeout in seconds. + * + */ +uint32_t otLinkCslGetTimeout(otInstance *aInstance); + +/** + * This function sets the CSL timeout. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aTimeout The CSL timeout in seconds. + * + * @retval OT_ERROR_NONE Successfully set the CSL timeout. + * @retval OT_ERROR_INVALID_ARGS Invalid CSL timeout. + * + */ +otError otLinkCslSetTimeout(otInstance *aInstance, uint32_t aTimeout); + /** * This function returns the current CCA (Clear Channel Assessment) failure rate. * diff --git a/include/openthread/platform/radio.h b/include/openthread/platform/radio.h index 23a5a5df4..703952ea8 100644 --- a/include/openthread/platform/radio.h +++ b/include/openthread/platform/radio.h @@ -143,6 +143,17 @@ typedef uint16_t otShortAddress; #define OT_EXT_ADDRESS_SIZE 8 ///< Size of an IEEE 802.15.4 Extended Address (bytes) +/** + * This enumeration defines constants about size of header IE in ACK. + * + */ +enum +{ + OT_IE_HEADER_IE_SIZE = 2, ///< Size of IE header in bytes. + OT_CSL_IE_SIZE = 4, ///< Size of CSL IE content in bytes. + OT_ACK_IE_MAX_SIZE = 16, ///< Max length for header IE in ACK. +}; + /** * @struct otExtAddress * @@ -213,10 +224,13 @@ typedef struct otRadioFrame { const otMacKey *mAesKey; ///< The key used for AES-CCM frame security. otRadioIeInfo * mIeInfo; ///< The pointer to the Header IE(s) related information. + uint16_t mPeriod; ///< The transmit time period. + uint16_t mPhase; ///< The transmit time phase. uint8_t mMaxCsmaBackoffs; ///< Maximum number of backoffs attempts before declaring CCA failure. uint8_t mMaxFrameRetries; ///< Maximum number of retries allowed after a transmission failure. bool mIsARetx : 1; ///< True if this frame is a retransmission (ignored by radio driver). bool mCsmaCaEnabled : 1; ///< Set to true to enable CSMA-CA for this packet, false otherwise. + bool mCslPresent : 1; ///< Set to true if CSL header ie is present. bool mIsSecurityProcessed : 1; ///< True if SubMac should skip the AES processing of this frame. } mTxInfo; @@ -866,6 +880,32 @@ bool otPlatRadioIsCoexEnabled(otInstance *aInstance); */ otError otPlatRadioGetCoexMetrics(otInstance *aInstance, otRadioCoexMetrics *aCoexMetrics); +/** + * Enable or disable CSL receiver. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aCslPeriod CSL period, 0 for disabling CSL. + * @param[in] aExtAddr The extended source address of CSL receiver's parent device (when the platforms generate + * enhanced ack, platforms may need to know acks to which address should include CSL IE). + * + * @retval OT_ERROR_NOT_SUPPORTED Radio driver doesn't support CSL. + * @retval OT_ERROR_FAILED Other platform specific errors. + * @retval OT_ERROR_NONE Successfully enabled or disabled CSL. + * + */ +otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, const otExtAddress *aExtAddr); + +/** + * Update CSL sample time in radio driver. + * + * Sample time is stored in radio driver as a copy to calculate phase when sending ACK with CSL IE. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aCslSampleTime The latest sample time. + * + */ +void otPlatRadioUpdateCslSampleTime(otInstance *aInstance, uint32_t aCslSampleTime); + /** * @} * diff --git a/script/check-simulation-build-autotools b/script/check-simulation-build-autotools index ed6811113..641218a5c 100755 --- a/script/check-simulation-build-autotools +++ b/script/check-simulation-build-autotools @@ -86,6 +86,7 @@ build_all_features() local options_1_2=( "-DOPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE=1" + "-DOPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE=1" "-DOPENTHREAD_CONFIG_DUA_ENABLE=1" ) diff --git a/src/cli/README.md b/src/cli/README.md index d021bfcd5..794ba44fd 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -33,6 +33,7 @@ Done - [commissioner](README_COMMISSIONER.md) - [contextreusedelay](#contextreusedelay) - [counters](#counters) +- [csl](#csl) - [dataset](README_DATASET.md) - [delaytimermin](#delaytimermin) - [diag](#diag) @@ -499,6 +500,45 @@ Done Done ``` +### csl + +Get the CSL configuration. + +```bash +> csl +Channel: 11 +Period: 1000 (in units of 10 symbols), 160ms +Timeout: 1000s +Done +``` + +### csl channel \ + +Set CSL channel. + +```bash +> csl channel 20 +Done +``` + +### csl period \ + +Set CSL period in units of 10 symbols. Disable CSL by setting this parameter to `0`. + +```bash +> csl period 3000 +Done +``` + +### csl timeout \ + +Set the CSL timeout in seconds. + +```bash +> csl timeout 10 +Done +``` + ### networktime Get the Thread network time and the time sync parameters. diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 361365961..02dc375e8 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -132,6 +132,9 @@ const struct Command Interpreter::sCommands[] = { {"contextreusedelay", &Interpreter::ProcessContextIdReuseDelay}, #endif {"counters", &Interpreter::ProcessCounters}, +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + {"csl", &Interpreter::ProcessCsl}, +#endif {"dataset", &Interpreter::ProcessDataset}, #if OPENTHREAD_FTD {"delaytimermin", &Interpreter::ProcessDelayTimerMin}, @@ -1283,6 +1286,44 @@ exit: AppendResult(error); } +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +void Interpreter::ProcessCsl(uint8_t aArgsLength, char *argv[]) +{ + otError error = OT_ERROR_INVALID_ARGS; + + if (aArgsLength == 0) + { + OutputFormat("Channel: %u\r\n", otLinkCslGetChannel(mInstance)); + OutputFormat("Period: %u(in units of 10 symbols), %ums\r\n", otLinkCslGetPeriod(mInstance), + otLinkCslGetPeriod(mInstance) * kUsPerTenSymbols / 1000); + OutputFormat("Timeout: %us\r\n", otLinkCslGetTimeout(mInstance)); + error = OT_ERROR_NONE; + } + else if (aArgsLength == 2) + { + long value; + + SuccessOrExit(error = ParseLong(argv[1], value)); + + if (strcmp(argv[0], "channel") == 0) + { + SuccessOrExit(error = otLinkCslSetChannel(mInstance, static_cast(value))); + } + else if (strcmp(argv[0], "period") == 0) + { + SuccessOrExit(error = otLinkCslSetPeriod(mInstance, static_cast(value))); + } + else if (strcmp(argv[0], "timeout") == 0) + { + SuccessOrExit(error = otLinkCslSetTimeout(mInstance, static_cast(value))); + } + } + +exit: + AppendResult(error); +} +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + #if OPENTHREAD_FTD void Interpreter::ProcessDelayTimerMin(uint8_t aArgsLength, char *aArgs[]) { diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 7b4d9e178..d635c419e 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -307,6 +307,7 @@ private: void ProcessContextIdReuseDelay(uint8_t aArgsLength, char *aArgs[]); #endif void ProcessCounters(uint8_t aArgsLength, char *aArgs[]); + void ProcessCsl(uint8_t aArgsLength, char *argv[]); #if OPENTHREAD_FTD void ProcessDelayTimerMin(uint8_t aArgsLength, char *aArgs[]); #endif diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 431f61cbd..94ccfcf21 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -173,6 +173,7 @@ set(COMMON_SOURCES thread/announce_begin_server.cpp thread/announce_sender.cpp thread/child_table.cpp + thread/csl_tx_scheduler.cpp thread/discover_scanner.cpp thread/dua_manager.cpp thread/energy_scan_server.cpp diff --git a/src/core/Makefile.am b/src/core/Makefile.am index e7a3ad5b5..f473507ab 100644 --- a/src/core/Makefile.am +++ b/src/core/Makefile.am @@ -215,6 +215,7 @@ SOURCES_COMMON = \ thread/announce_begin_server.cpp \ thread/announce_sender.cpp \ thread/child_table.cpp \ + thread/csl_tx_scheduler.cpp \ thread/discover_scanner.cpp \ thread/dua_manager.cpp \ thread/energy_scan_server.cpp \ @@ -431,6 +432,7 @@ HEADERS_COMMON = \ thread/announce_sender.hpp \ thread/child_mask.hpp \ thread/child_table.hpp \ + thread/csl_tx_scheduler.hpp \ thread/discover_scanner.hpp \ thread/dua_manager.hpp \ thread/energy_scan_server.hpp \ diff --git a/src/core/api/link_api.cpp b/src/core/api/link_api.cpp index a6ea3e76e..c3910c5dc 100644 --- a/src/core/api/link_api.cpp +++ b/src/core/api/link_api.cpp @@ -481,3 +481,58 @@ uint16_t otLinkGetCcaFailureRate(otInstance *aInstance) return instance.Get().GetCcaFailureRate(); } + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +uint8_t otLinkCslGetChannel(otInstance *aInstance) +{ + return static_cast(aInstance)->Get().GetCslChannel(); +} + +otError otLinkCslSetChannel(otInstance *aInstance, uint8_t aChannel) +{ + otError error = OT_ERROR_NONE; + Instance &instance = *static_cast(aInstance); + + VerifyOrExit((Radio::kChannelMin <= aChannel) && (aChannel <= Radio::kChannelMax), error = OT_ERROR_INVALID_ARGS); + + instance.Get().SetCslChannel(aChannel); + +exit: + return error; +} + +uint16_t otLinkCslGetPeriod(otInstance *aInstance) +{ + return static_cast(aInstance)->Get().GetCslPeriod(); +} + +otError otLinkCslSetPeriod(otInstance *aInstance, uint16_t aPeriod) +{ + otError error = OT_ERROR_NONE; + Instance &instance = *static_cast(aInstance); + + VerifyOrExit((aPeriod == 0 || kMinCslPeriod <= aPeriod), error = OT_ERROR_INVALID_ARGS); + instance.Get().SetCslPeriod(aPeriod); + +exit: + return error; +} + +uint32_t otLinkCslGetTimeout(otInstance *aInstance) +{ + return static_cast(aInstance)->Get().GetCslTimeout(); +} + +otError otLinkCslSetTimeout(otInstance *aInstance, uint32_t aTimeout) +{ + otError error = OT_ERROR_NONE; + Instance &instance = *static_cast(aInstance); + + VerifyOrExit(kMaxCslTimeout >= aTimeout, error = OT_ERROR_INVALID_ARGS); + instance.Get().SetCslTimeout(aTimeout); + +exit: + return error; +} + +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE diff --git a/src/core/common/instance.hpp b/src/core/common/instance.hpp index b31ea2c1f..eb15e5f73 100644 --- a/src/core/common/instance.hpp +++ b/src/core/common/instance.hpp @@ -524,6 +524,13 @@ template <> inline DataPollHandler &Instance::Get(void) return mThreadNetif.mMeshForwarder.mIndirectSender.mDataPollHandler; } +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +template <> inline CslTxScheduler &Instance::Get(void) +{ + return mThreadNetif.mMeshForwarder.mIndirectSender.mCslTxScheduler; +} +#endif + template <> inline AddressResolver &Instance::Get(void) { return mThreadNetif.mAddressResolver; diff --git a/src/core/config/mac.h b/src/core/config/mac.h index 34349db7e..081ea87ac 100644 --- a/src/core/config/mac.h +++ b/src/core/config/mac.h @@ -326,4 +326,48 @@ (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2) && OPENTHREAD_MTD #endif +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + +/** + * @def OPENTHREAD_CONFIG_MAC_CSL_MIN_PERIOD + * + * This setting configures the minimum CSL period that could be used, in units of milliseconds. + * + */ +#ifndef OPENTHREAD_CONFIG_MAC_CSL_MIN_PERIOD +#define OPENTHREAD_CONFIG_MAC_CSL_MIN_PERIOD 10 +#endif + +/** + * @def OPENTHREAD_CONFIG_MAC_CSL_MAX_TIMEOUT + * + * This setting configures the maximum CSL timeout that could be used, in units of seconds. + * + */ +#ifndef OPENTHREAD_CONFIG_MAC_CSL_MAX_TIMEOUT +#define OPENTHREAD_CONFIG_MAC_CSL_MAX_TIMEOUT 10000 +#endif + +/** + * @def OPENTHREAD_CONFIG_CSL_TIMEOUT + * + * The default CSL timeout in seconds. + * + */ +#ifndef OPENTHREAD_CONFIG_CSL_TIMEOUT +#define OPENTHREAD_CONFIG_CSL_TIMEOUT 100 +#endif + +/** + * @def OPENTHREAD_CONFIG_CSL_SAMPLE_WINDOW + * + * The CSL sample window in 10 symbols. + * + */ +#ifndef OPENTHREAD_CONFIG_CSL_SAMPLE_WINDOW +#define OPENTHREAD_CONFIG_CSL_SAMPLE_WINDOW 5 +#endif + +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + #endif // CONFIG_MAC_H_ diff --git a/src/core/mac/data_poll_handler.hpp b/src/core/mac/data_poll_handler.hpp index 621f51f68..c087149ef 100644 --- a/src/core/mac/data_poll_handler.hpp +++ b/src/core/mac/data_poll_handler.hpp @@ -89,6 +89,9 @@ public: class ChildInfo { friend class DataPollHandler; +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + friend class CslTxScheduler; +#endif private: bool IsDataPollPending(void) const { return mDataPollPending; } diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index ef84ec563..75c022d02 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -46,6 +46,7 @@ #include "crypto/sha256.hpp" #include "mac/mac_frame.hpp" #include "radio/radio.hpp" +#include "thread/child_table.hpp" #include "thread/link_quality.hpp" #include "thread/mle_router.hpp" #include "thread/thread_netif.hpp" @@ -111,6 +112,9 @@ Mac::Mac(Instance &aInstance) , mMaxFrameRetriesDirect(kDefaultMaxFrameRetriesDirect) #if OPENTHREAD_FTD , mMaxFrameRetriesIndirect(kDefaultMaxFrameRetriesIndirect) +#endif +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + , mCslTxFireTime(TimeMilli::kMaxDuration) #endif , mActiveScanHandler(nullptr) // Initialize `mActiveScanHandler` and `mEnergyScanHandler` union , mScanHandlerContext(nullptr) @@ -207,6 +211,9 @@ bool Mac::IsInTransmitState(void) const case kOperationTransmitDataDirect: #if OPENTHREAD_FTD case kOperationTransmitDataIndirect: +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + case kOperationTransmitDataCsl: +#endif #endif case kOperationTransmitBeacon: case kOperationTransmitPoll: @@ -561,10 +568,24 @@ void Mac::RequestIndirectFrameTransmission(void) StartOperation(kOperationTransmitDataIndirect); +exit: + return; +} + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +void Mac::RequestCslFrameTransmission(uint32_t aDelay) +{ + VerifyOrExit(mEnabled, OT_NOOP); + + mCslTxFireTime = mTimer.GetNow() + aDelay; + + StartOperation(kOperationTransmitDataCsl); + exit: return; } #endif +#endif // OPENTHREAD_FTD otError Mac::RequestOutOfBandFrameTransmission(otRadioFrame *aOobFrame) { @@ -608,23 +629,39 @@ void Mac::UpdateIdleMode(void) VerifyOrExit(mOperation == kOperationIdle, OT_NOOP); + if (!mRxOnWhenIdle) + { #if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS - if (mShouldDelaySleep) - { - mTimer.Start(kSleepDelay); - mShouldDelaySleep = false; - mDelayingSleep = true; - otLogDebgMac("Idle mode: Sleep delayed"); - } + if (mShouldDelaySleep) + { + mTimer.Start(kSleepDelay); + mShouldDelaySleep = false; + mDelayingSleep = true; + otLogDebgMac("Idle mode: Sleep delayed"); + } - if (mDelayingSleep) + if (mDelayingSleep) + { + shouldSleep = false; + } +#endif + } +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + else if (mPendingTransmitDataCsl) { - shouldSleep = false; + mTimer.FireAt(mCslTxFireTime); } #endif if (shouldSleep) { +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (IsCslEnabled()) + { + IgnoreError(mSubMac.CslSample()); + ExitNow(); + } +#endif IgnoreError(mSubMac.Sleep()); otLogDebgMac("Idle mode: Radio sleeping"); } @@ -680,6 +717,12 @@ void Mac::StartOperation(Operation aOperation) case kOperationTransmitDataIndirect: mPendingTransmitDataIndirect = true; break; + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + case kOperationTransmitDataCsl: + mPendingTransmitDataCsl = true; + break; +#endif #endif case kOperationTransmitPoll: @@ -720,6 +763,9 @@ void Mac::PerformNextOperation(void) mPendingTransmitDataDirect = false; #if OPENTHREAD_FTD mPendingTransmitDataIndirect = false; +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + mPendingTransmitDataCsl = false; +#endif #endif mPendingTransmitPoll = false; mTimer.Stop(); @@ -738,6 +784,13 @@ void Mac::PerformNextOperation(void) mPendingWaitingForData = false; mOperation = kOperationWaitingForData; } +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + else if (mPendingTransmitDataCsl && mTimer.GetNow() >= mCslTxFireTime) + { + mPendingTransmitDataCsl = false; + mOperation = kOperationTransmitDataCsl; + } +#endif else if (mPendingTransmitOobFrame) { mPendingTransmitOobFrame = false; @@ -764,7 +817,7 @@ void Mac::PerformNextOperation(void) mPendingTransmitDataIndirect = false; mOperation = kOperationTransmitDataIndirect; } -#endif +#endif // OPENTHREAD_FTD else if (mPendingTransmitPoll && (!mPendingTransmitDataDirect || mShouldTxPollBeforeData)) { mPendingTransmitPoll = false; @@ -806,6 +859,9 @@ void Mac::PerformNextOperation(void) case kOperationTransmitDataDirect: #if OPENTHREAD_FTD case kOperationTransmitDataIndirect: +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + case kOperationTransmitDataCsl: +#endif #endif case kOperationTransmitPoll: case kOperationTransmitOutOfBandFrame: @@ -837,7 +893,7 @@ otError Mac::PrepareDataRequest(TxFrame &aFrame) VerifyOrExit(!dst.IsNone(), error = OT_ERROR_ABORT); fcf = Frame::kFcfFrameMacCmd | Frame::kFcfPanidCompression | Frame::kFcfAckRequest | Frame::kFcfSecurityEnabled; - UpdateFrameControlField(/* aIsTimeSync */ false, fcf); + UpdateFrameControlField(nullptr, /* aIsTimeSync */ false, fcf); if (dst.IsExtended()) { @@ -859,6 +915,10 @@ otError Mac::PrepareDataRequest(TxFrame &aFrame) aFrame.SetSrcAddr(src); aFrame.SetDstAddr(dst); +#if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT + IgnoreError(AppendHeaderIe(false, aFrame)); +#endif + IgnoreError(aFrame.SetCommandId(Frame::kMacCmdDataRequest)); exit: @@ -1006,6 +1066,11 @@ void Mac::ProcessTransmitSecurity(TxFrame &aFrame) VerifyOrExit(aFrame.GetTimeIeOffset() == 0, OT_NOOP); #endif +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + // Transmit security will be processed after time IE content is updated. + VerifyOrExit(aFrame.mInfo.mTxInfo.mCslPresent == 0, OT_NOOP); +#endif + aFrame.ProcessTransmitAesCcm(*extAddress); exit: @@ -1020,6 +1085,9 @@ void Mac::BeginTransmit(void) VerifyOrExit(IsEnabled(), error = OT_ERROR_ABORT); sendFrame.SetIsARetransmission(false); sendFrame.SetIsSecurityProcessed(false); +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + sendFrame.SetTxPeriod(0); +#endif switch (mOperation) { @@ -1069,7 +1137,22 @@ void Mac::BeginTransmit(void) sendFrame.SetSequence(mDataSequence++); } break; + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + case kOperationTransmitDataCsl: + sendFrame.SetMaxCsmaBackoffs(kMaxCsmaBackoffsCsl); + sendFrame.SetMaxFrameRetries(kMaxFrameRetriesCsl); + SuccessOrExit(error = Get().HandleFrameRequest(sendFrame)); + // If the frame is marked as a retransmission, then data sequence number is already set. + if (!sendFrame.IsARetransmission()) + { + sendFrame.SetSequence(mDataSequence++); + } + + break; + #endif +#endif // OPENTHREAD_FTD case kOperationTransmitOutOfBandFrame: sendFrame.CopyFrom(*mOobFrame); @@ -1213,6 +1296,14 @@ void Mac::RecordFrameTransmitStatus(const TxFrame &aFrame, if ((aError == OT_ERROR_NONE) && ackRequested && (aAckFrame != nullptr) && (neighbor != nullptr)) { neighbor->GetLinkInfo().AddRss(aAckFrame->GetRssi()); +#if OPENTHREAD_FTD + if (aAckFrame->GetVersion() == Frame::kFcfFrameVersion2015) + { +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + ProcessCsl(*aAckFrame, dstAddr); +#endif + } +#endif // OPENTHREAD_FTD } // Update MAC counters. @@ -1345,6 +1436,17 @@ void Mac::HandleTransmitDone(TxFrame &aFrame, RxFrame *aAckFrame, otError aError break; #if OPENTHREAD_FTD +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + case kOperationTransmitDataCsl: + mCounters.mTxData++; + + otDumpDebgMac("TX", aFrame.GetHeader(), aFrame.GetLength()); + FinishOperation(); + Get().HandleSentFrame(aFrame, aError); + PerformNextOperation(); + + break; +#endif case kOperationTransmitDataIndirect: mCounters.mTxData++; @@ -1402,17 +1504,25 @@ void Mac::HandleTimer(void) PerformNextOperation(); break; -#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS case kOperationIdle: - if (mDelayingSleep) + if (!mRxOnWhenIdle) { - otLogDebgMac("Sleep delay timeout expired"); - mDelayingSleep = false; - UpdateIdleMode(); - } - - break; +#if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS + if (mDelayingSleep) + { + otLogDebgMac("Sleep delay timeout expired"); + mDelayingSleep = false; + UpdateIdleMode(); + } #endif + } +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + else if (mPendingTransmitDataCsl) + { + PerformNextOperation(); + } +#endif + break; default: OT_ASSERT(false); @@ -1689,6 +1799,13 @@ void Mac::HandleReceivedFrame(RxFrame *aFrame, otError aError) ExitNow(); } +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + if (aFrame->GetVersion() == Frame::kFcfFrameVersion2015) + { + ProcessCsl(*aFrame, srcaddr); + } +#endif + Get().ProcessFrame(*aFrame); if (neighbor != nullptr) @@ -1797,6 +1914,8 @@ void Mac::HandleReceivedFrame(RxFrame *aFrame, otError aError) otDumpDebgMac("RX", aFrame->GetHeader(), aFrame->GetLength()); Get().HandleReceivedFrame(*aFrame); + UpdateIdleMode(); + exit: if (error != OT_ERROR_NONE) @@ -1965,6 +2084,12 @@ const char *Mac::OperationToString(Operation aOperation) case kOperationTransmitDataIndirect: retval = "TransmitDataIndirect"; break; + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + case kOperationTransmitDataCsl: + retval = "TransmitDataCsl"; + break; +#endif #endif case kOperationTransmitPoll: @@ -2061,8 +2186,87 @@ exit: } #endif +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +void Mac::SetCslChannel(uint8_t aChannel) +{ + VerifyOrExit(GetCslChannel() != aChannel, OT_NOOP); + + mSubMac.SetCslChannel(aChannel); + + if (IsCslEnabled()) + { + Get().ScheduleChildUpdateRequest(); + } +exit: + return; +} + +void Mac::SetCslPeriod(uint16_t aPeriod) +{ + mSubMac.SetCslPeriod(aPeriod); + IgnoreError(Get().EnableCsl(GetCslPeriod(), &Get().GetParent().GetExtAddress())); + + if (IsCslEnabled()) + { + Get().ScheduleChildUpdateRequest(); + } + + UpdateIdleMode(); +} + +void Mac::SetCslTimeout(uint32_t aTimeout) +{ + VerifyOrExit(GetCslTimeout() != aTimeout, OT_NOOP); + + mSubMac.SetCslTimeout(aTimeout); + + if (IsCslEnabled()) + { + Get().ScheduleChildUpdateRequest(); + } + +exit: + return; +} + +bool Mac::IsCslEnabled(void) const +{ + return (GetCslPeriod() > 0) && !GetRxOnWhenIdle() && Get().IsChild() && + Get().GetParent().IsEnhancedKeepAliveSupported(); +} + +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +void Mac::ProcessCsl(const RxFrame &aFrame, const Address &aSrcAddr) +{ + const uint8_t *cur = aFrame.GetHeaderIe(Frame::kHeaderIeCsl); + Child * child = Get().FindChild(aSrcAddr, Child::kInStateAnyExceptInvalid); + const CslIe * csl; + + VerifyOrExit(cur != nullptr && child != nullptr && aFrame.GetSecurityEnabled(), OT_NOOP); + + csl = reinterpret_cast(cur + sizeof(HeaderIe)); + + child->SetCslPeriod(csl->GetPeriod()); + // Use ceiling to ensure the the time diff will be within kUsPerTenSymbols + child->SetCslPhase(((aFrame.GetTimestamp() + kUsPerTenSymbols - 1) / kUsPerTenSymbols + csl->GetPhase()) % + csl->GetPeriod()); + child->SetCslSynchronized(true); + child->SetCslLastHeard(TimerMilli::GetNow()); + otLogDebgMac("Timestamp=%u Sequence=%u CslPeriod=%hu CslPhase=%hu TransmitPhase=%hu", + static_cast(aFrame.GetTimestamp()), aFrame.GetSequence(), csl->GetPeriod(), csl->GetPhase(), + child->GetCslPhase()); + + Get().Update(); + +exit: + return; +} +#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT -otError Mac::AppendHeaderIe(bool aIsTimeSync, TxFrame &aFrame) +otError Mac::AppendHeaderIe(bool aIsTimeSync, TxFrame &aFrame) const { OT_UNUSED_VARIABLE(aIsTimeSync); @@ -2083,6 +2287,23 @@ otError Mac::AppendHeaderIe(bool aIsTimeSync, TxFrame &aFrame) } #endif +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (IsCslEnabled()) + { + OT_ASSERT(aFrame.GetSecurityEnabled()); + + aFrame.mInfo.mTxInfo.mCslPresent = true; + ieList[ieCount].Init(); + ieList[ieCount].SetId(Frame::kHeaderIeCsl); + ieList[ieCount].SetLength(sizeof(CslIe)); + ieCount++; + } + else +#endif + { + aFrame.mInfo.mTxInfo.mCslPresent = false; + } + if (ieCount > 0) { ieList[ieCount].Init(); @@ -2107,9 +2328,10 @@ exit: } #endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT -void Mac::UpdateFrameControlField(bool aIsTimeSync, uint16_t &aFcf) +void Mac::UpdateFrameControlField(const Neighbor *aNeighbor, bool aIsTimeSync, uint16_t &aFcf) const { OT_UNUSED_VARIABLE(aIsTimeSync); + OT_UNUSED_VARIABLE(aNeighbor); #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE @@ -2119,6 +2341,21 @@ void Mac::UpdateFrameControlField(bool aIsTimeSync, uint16_t &aFcf) } else #endif +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (IsCslEnabled()) + { + aFcf |= Frame::kFcfFrameVersion2015 | Frame::kFcfIePresent; + } + else +#endif +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + if (aNeighbor != nullptr && !Get().IsActiveRouter(aNeighbor->GetRloc16()) && + static_cast(aNeighbor)->IsCslSynchronized()) + { + aFcf |= Frame::kFcfFrameVersion2015; + } + else +#endif #endif { aFcf |= Frame::kFcfFrameVersion2006; diff --git a/src/core/mac/mac.hpp b/src/core/mac/mac.hpp index c2ae35d06..7c66b00fb 100644 --- a/src/core/mac/mac.hpp +++ b/src/core/mac/mac.hpp @@ -41,6 +41,7 @@ #include "common/locator.hpp" #include "common/tasklet.hpp" +#include "common/time.hpp" #include "common/timer.hpp" #include "mac/channel_mask.hpp" #include "mac/mac_filter.hpp" @@ -81,12 +82,13 @@ enum OPENTHREAD_CONFIG_MAC_MAX_CSMA_BACKOFFS_DIRECT, ///< macMaxCsmaBackoffs for direct transmissions kMaxCsmaBackoffsIndirect = OPENTHREAD_CONFIG_MAC_MAX_CSMA_BACKOFFS_INDIRECT, ///< macMaxCsmaBackoffs for indirect transmissions + kMaxCsmaBackoffsCsl = 0, ///< macMaxCsmaBackoffs for CSL transmissions kDefaultMaxFrameRetriesDirect = OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_DIRECT, ///< macDefaultMaxFrameRetries for direct transmissions kDefaultMaxFrameRetriesIndirect = OPENTHREAD_CONFIG_MAC_DEFAULT_MAX_FRAME_RETRIES_INDIRECT, ///< macDefaultMaxFrameRetries for indirect - ///< transmissions + kMaxFrameRetriesCsl = 0, ///< macMaxFrameRetries for CSL transmissions kTxNumBcast = OPENTHREAD_CONFIG_MAC_TX_NUM_BCAST ///< Number of times each broadcast frame is transmitted }; @@ -215,6 +217,17 @@ public: * */ void RequestIndirectFrameTransmission(void); + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + /** + * This method requests `Mac` to start a Csl Tx operation after a delay of @p aDelay time. + * + * @param[in] aDelay Delay time for `Mac` to start a Csl Tx, in units of milliseconds. + * + */ + void RequestCslFrameTransmission(uint32_t aDelay); +#endif + #endif /** @@ -663,6 +676,65 @@ public: */ bool IsEnabled(void) const { return mEnabled; } +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + /** + * This method gets the CSL channel. + * + * @returns CSL channel. + * + */ + uint8_t GetCslChannel(void) const { return mSubMac.GetCslChannel(); } + + /** + * This method sets the CSL channel. + * + * @param[in] aChannel The CSL channel. + * + */ + void SetCslChannel(uint8_t aChannel); + + /** + * This method gets the CSL period. + * + * @returns CSL period in units of 10 symbols. + * + */ + uint16_t GetCslPeriod(void) const { return mSubMac.GetCslPeriod(); } + + /** + * This method sets the CSL period. + * + * @param[in] aPeriod The CSL period in 10 symbols. + * + */ + void SetCslPeriod(uint16_t aPeriod); + + /** + * This method gets the CSL timeout. + * + * @returns CSL timeout in seconds. + * + */ + uint32_t GetCslTimeout(void) const { return mSubMac.GetCslTimeout(); } + + /** + * This method sets the CSL timeout. + * + * @param[in] aTimeout The CSL timeout in seconds. + * + */ + void SetCslTimeout(uint32_t aTimeout); + + /** + * This method indicates whether CSL is started at the moment. + * + * @retval TURE if CSL is actually running at the moment, FALSE otherwise. + * + */ + bool IsCslEnabled(void) const; + +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT /** * This method appends header IEs to a TX-frame according to its @@ -675,8 +747,8 @@ public: * @retval OT_ERROR_NOT_FOUND If cannot find header IE position in the frame. * */ - static otError AppendHeaderIe(bool aIsTimeSync, TxFrame &aFrame); -#endif + otError AppendHeaderIe(bool aIsTimeSync, TxFrame &aFrame) const; +#endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT /** * This method updates frame control field. @@ -685,11 +757,12 @@ public: * If this is a csl transmission frame or header IE is present in this frame, * the version should be set to 2015. Otherwise, the version would be set to 2006. * + * @param[in] aNeighbor A pointer to the destination device, could be `nullptr`. * @param[in] aIsTimeSync A boolean indicates if time sync is being used. * @param[out] aFcf A reference to the frame control field to set. * */ - static void UpdateFrameControlField(bool aIsTimeSync, uint16_t &aFcf); + void UpdateFrameControlField(const Neighbor *aNeighbor, bool aIsTimeSync, uint16_t &aFcf) const; private: enum @@ -708,6 +781,9 @@ private: kOperationTransmitDataDirect, #if OPENTHREAD_FTD kOperationTransmitDataIndirect, +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + kOperationTransmitDataCsl, +#endif #endif kOperationTransmitPoll, kOperationWaitingForData, @@ -737,8 +813,8 @@ private: }; #endif // OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE - void ProcessTransmitSecurity(TxFrame &aFrame); otError ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neighbor *aNeighbor); + void ProcessTransmitSecurity(TxFrame &aFrame); void UpdateIdleMode(void); void StartOperation(Operation aOperation); void FinishOperation(void); @@ -771,6 +847,9 @@ private: uint8_t GetTimeIeOffset(const Frame &aFrame); #endif +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + void ProcessCsl(const RxFrame &aFrame, const Address &aSrcAddr); +#endif static const char *OperationToString(Operation aOperation); static const otMacKey sMode2Key; @@ -786,6 +865,9 @@ private: bool mPendingTransmitDataDirect : 1; #if OPENTHREAD_FTD bool mPendingTransmitDataIndirect : 1; +#endif +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + bool mPendingTransmitDataCsl : 1; #endif bool mPendingTransmitPoll : 1; bool mPendingTransmitOobFrame : 1; @@ -820,6 +902,9 @@ private: #if OPENTHREAD_FTD uint8_t mMaxFrameRetriesIndirect; #endif +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + TimeMilli mCslTxFireTime; +#endif union { diff --git a/src/core/mac/mac_frame.cpp b/src/core/mac/mac_frame.cpp index 2713bbfbf..7d45d7981 100644 --- a/src/core/mac/mac_frame.cpp +++ b/src/core/mac/mac_frame.cpp @@ -941,11 +941,13 @@ void Frame::SetCslIe(uint16_t aCslPeriod, uint16_t aCslPhase) uint8_t *cur = GetHeaderIe(Frame::kHeaderIeCsl); CslIe * csl; - OT_ASSERT(cur != nullptr); + VerifyOrExit(cur != nullptr, OT_NOOP); csl = reinterpret_cast(cur + sizeof(HeaderIe)); csl->SetPeriod(aCslPeriod); csl->SetPhase(aCslPhase); +exit: + return; } #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE diff --git a/src/core/mac/mac_frame.hpp b/src/core/mac/mac_frame.hpp index 8be620b5f..76ffbd89c 100644 --- a/src/core/mac/mac_frame.hpp +++ b/src/core/mac/mac_frame.hpp @@ -1296,6 +1296,11 @@ public: * */ otError GenerateEnhAck(const RxFrame &aFrame, bool aIsFramePending, const uint8_t *aIeData, uint8_t aIeLength); + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + void SetTxPhase(uint16_t aPhase) { mInfo.mTxInfo.mPhase = aPhase; } + void SetTxPeriod(uint16_t aPeriod) { mInfo.mTxInfo.mPeriod = aPeriod; } +#endif }; OT_TOOL_PACKED_BEGIN diff --git a/src/core/mac/sub_mac.cpp b/src/core/mac/sub_mac.cpp index 9a4e65065..063f63cad 100644 --- a/src/core/mac/sub_mac.cpp +++ b/src/core/mac/sub_mac.cpp @@ -35,6 +35,9 @@ #include +#include + +#include "mac_frame.hpp" #include "common/code_utils.hpp" #include "common/debug.hpp" #include "common/instance.hpp" @@ -62,6 +65,13 @@ SubMac::SubMac(Instance &aInstance) , mFrameCounter(0) , mKeyId(0) , mTimer(aInstance, SubMac::HandleTimer, this) +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + , mCslTimeout(OPENTHREAD_CONFIG_CSL_TIMEOUT) + , mCslPeriod(0) + , mCslChannel(OPENTHREAD_CONFIG_DEFAULT_CHANNEL) + , mCslState(kCslIdle) + , mCslTimer(aInstance, SubMac::HandleCslTimer, this) +#endif { mExtAddress.Clear(); mPrevKey.Clear(); @@ -143,6 +153,7 @@ otError SubMac::Enable(void) SuccessOrExit(error = Get().Enable()); SuccessOrExit(error = Get().Sleep()); + SetState(kStateSleep); exit: @@ -195,6 +206,36 @@ exit: return error; } +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +otError SubMac::CslSample(void) +{ + otError error = OT_ERROR_NONE; + + switch (mCslState) + { + case kCslSample: + error = Get().Receive(mCslChannel); + break; + case kCslSleep: + error = Get().Sleep(); + break; + case kCslIdle: + ExitNow(error = OT_ERROR_INVALID_STATE); + default: + OT_ASSERT(false); + } + + SetState(kStateCslSample); + +exit: + if (error != OT_ERROR_NONE) + { + otLogWarnMac("CslSample() failed, error: %s", otThreadErrorToString(error)); + } + return error; +} +#endif + void SubMac::HandleReceiveDone(RxFrame *aFrame, otError aError) { if (mPcapCallback && (aFrame != nullptr) && (aError == OT_ERROR_NONE)) @@ -218,6 +259,9 @@ otError SubMac::Send(void) { case kStateDisabled: case kStateCsmaBackoff: +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + case kStateCslTransmit: +#endif case kStateTransmit: case kStateEnergyScan: ExitNow(error = OT_ERROR_INVALID_STATE); @@ -225,6 +269,9 @@ otError SubMac::Send(void) case kStateSleep: case kStateReceive: +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + case kStateCslSample: +#endif break; } @@ -278,6 +325,30 @@ void SubMac::StartCsmaBackoff(void) uint32_t backoff; uint32_t backoffExponent = kMinBE + mTransmitRetries + mCsmaBackoffs; +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + if (mTransmitFrame.mInfo.mTxInfo.mPeriod != 0) + { + uint32_t phaseNow = (otPlatTimeGet() / kUsPerTenSymbols) % mTransmitFrame.mInfo.mTxInfo.mPeriod; + uint32_t phaseDesired = mTransmitFrame.mInfo.mTxInfo.mPhase; + + SetState(kStateCslTransmit); + + if (phaseNow < phaseDesired) + { + mTimer.Start((phaseDesired - phaseNow) * kUsPerTenSymbols); + } + else if (phaseNow > phaseDesired) + { + mTimer.Start((phaseDesired + mTransmitFrame.mInfo.mTxInfo.mPeriod - phaseNow) * kUsPerTenSymbols); + } + else + { + BeginTransmit(); + } + + ExitNow(); + } +#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE SetState(kStateCsmaBackoff); VerifyOrExit(ShouldHandleCsmaBackOff(), BeginTransmit()); @@ -315,9 +386,13 @@ void SubMac::BeginTransmit(void) OT_UNUSED_VARIABLE(error); +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + VerifyOrExit(mState == kStateCsmaBackoff || mState == kStateCslTransmit, OT_NOOP); +#else VerifyOrExit(mState == kStateCsmaBackoff, OT_NOOP); +#endif - mTransmitFrame.SetCsmaCaEnabled(true); + mTransmitFrame.SetCsmaCaEnabled(mTransmitFrame.mInfo.mTxInfo.mPeriod != 0); if ((mRadioCaps & OT_RADIO_CAPS_SLEEP_TO_TX) == 0) { @@ -454,11 +529,17 @@ otError SubMac::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration) case kStateDisabled: case kStateCsmaBackoff: case kStateTransmit: +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + case kStateCslTransmit: +#endif case kStateEnergyScan: ExitNow(error = OT_ERROR_INVALID_STATE); case kStateReceive: case kStateSleep: +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + case kStateCslSample: +#endif break; } @@ -529,6 +610,11 @@ void SubMac::HandleTimer(void) { switch (mState) { +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + case kStateCslTransmit: + BeginTransmit(); + break; +#endif case kStateCsmaBackoff: BeginTransmit(); break; @@ -723,6 +809,16 @@ const char *SubMac::StateToString(State aState) case kStateEnergyScan: str = "EnergyScan"; break; +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + case kStateCslTransmit: + str = "CslTransmit"; + break; +#endif +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + case kStateCslSample: + str = "CslSample"; + break; +#endif } return str; @@ -730,5 +826,131 @@ const char *SubMac::StateToString(State aState) // LCOV_EXCL_STOP +//--------------------------------------------------------------------------------------------------------------------- +// CSL Receiver methods + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +void SubMac::SetCslChannel(uint8_t aChannel) +{ + mCslChannel = aChannel; +} + +void SubMac::SetCslPeriod(uint16_t aPeriod) +{ + VerifyOrExit(mCslPeriod != aPeriod, OT_NOOP); + + mCslPeriod = aPeriod; + + mCslTimer.Stop(); + + if (mCslPeriod > 0) + { + mCslSampleTime = mCslTimer.GetNow(); + Get().UpdateCslSampleTime(mCslSampleTime.GetValue()); + + mCslState = kCslSample; + HandleCslTimer(); + } + else + { + mCslState = kCslIdle; + + if (mState == kStateCslSample) + { + IgnoreError(Get().Sleep()); + SetState(kStateSleep); + } + } + + otLogDebgMac("Csl Period: %u", mCslPeriod); + +exit: + return; +} + +void SubMac::SetCslTimeout(uint32_t aTimeout) +{ + mCslTimeout = aTimeout; +} + +void SubMac::FillCsl(Frame &aFrame) +{ + uint8_t *cur = aFrame.GetHeaderIe(Frame::kHeaderIeCsl); + + if (cur != nullptr) + { + CslIe *csl = reinterpret_cast(cur + sizeof(HeaderIe)); + + csl->SetPeriod(mCslPeriod); + csl->SetPhase(GetCslPhase()); +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + otLogDebgMac("%10u:FillCsl() seq=%u phase=%hu", static_cast(otPlatTimeGet()), aFrame.GetSequence(), + csl->GetPhase()); +#endif + } +} + +void SubMac::HandleCslTimer(Timer &aTimer) +{ + aTimer.GetOwner().HandleCslTimer(); +} + +void SubMac::HandleCslTimer(void) +{ + switch (mCslState) + { + case kCslSample: + mCslState = kCslSleep; + // kUsPerTenSymbols: computing CSL Phase using floor division. + mCslTimer.StartAt(mCslSampleTime, mCslPeriod * kUsPerTenSymbols - kUsPerTenSymbols); + if (mState == kStateCslSample) + { + IgnoreError(Get().Sleep()); + otLogDebgMac("CSL sleep %u", mCslTimer.GetNow().GetValue()); + } + break; + + case kCslSleep: + mCslState = kCslSample; + + mCslSampleTime += mCslPeriod * kUsPerTenSymbols; + Get().UpdateCslSampleTime(mCslSampleTime.GetValue()); + + mCslTimer.StartAt(mCslSampleTime, kCslSampleWindow); + + if (mState == kStateCslSample) + { + IgnoreError(Get().Receive(mCslChannel)); + otLogDebgMac("CSL Sample %u", mCslTimer.GetNow().GetValue()); + } + break; + + case kCslIdle: + break; + + default: + OT_ASSERT(false); + break; + } +} + +uint16_t SubMac::GetCslPhase(void) const +{ + TimeMicro now = TimerMicro::GetNow(); + uint32_t delta; + + if (mCslSampleTime < now) + { + delta = mCslSampleTime + mCslPeriod * kUsPerTenSymbols - now; + } + else + { + delta = mCslSampleTime - now; + } + + return static_cast(delta / kUsPerTenSymbols); +} +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + } // namespace Mac } // namespace ot diff --git a/src/core/mac/sub_mac.hpp b/src/core/mac/sub_mac.hpp index 05875c967..eb8e68337 100644 --- a/src/core/mac/sub_mac.hpp +++ b/src/core/mac/sub_mac.hpp @@ -297,6 +297,22 @@ public: */ otError Receive(uint8_t aChannel); +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + /** + * This method lets `SubMac` start CSL sample. + * + * `SubMac` would switch the radio state between `Receive` and `Sleep` according the CSL timer. When CslSample is + * started, `mState` will become `kStateCslSample`. But it could be doing `Sleep` or `Receive` at this moment + * (depending on `mCslState`). + * + * @retval OT_ERROR_NONE Successfully entered CSL operation (sleep or receive according to CSL timer). + * @retval OT_ERROR_BUSY The radio was transmitting. + * @retval OT_ERROR_INVALID_STATE The radio was disabled. + * + */ + otError CslSample(void); +#endif + /** * This method gets the radio transmit frame. * @@ -355,6 +371,65 @@ public: */ int8_t GetNoiseFloor(void); +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + + /** + * This method gets the CSL channel. + * + * @returns CSL channel. + * + */ + uint8_t GetCslChannel(void) const { return mCslChannel; } + + /** + * This method sets the CSL channel. + * + * @param[in] aChannel The CSL channel. + * + */ + void SetCslChannel(uint8_t aChannel); + + /** + * This method gets the CSL period. + * + * @returns CSL period. + * + */ + uint16_t GetCslPeriod(void) const { return mCslPeriod; } + + /** + * This method sets the CSL period. + * + * @param[in] aPeriod The CSL period in 10 symbols. + * + */ + void SetCslPeriod(uint16_t aPeriod); + + /** + * This method gets the CSL timeout. + * + * @returns CSL timeout + * + */ + uint32_t GetCslTimeout(void) const { return mCslTimeout; } + + /** + * This method sets the CSL timeout. + * + * @param[in] aTimeout The CSL timeout in seconds. + * + */ + void SetCslTimeout(uint32_t aTimeout); + + /** + * This method fills the CSL parameter to the frame. + * + * @param[inout] aFrame A reference to the frame to fill CSL parameter. + * + */ + void FillCsl(Frame &aFrame); +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + /** * This method sets MAC keys and key index. * @@ -408,6 +483,12 @@ public: void SetFrameCounter(uint32_t aFrameCounter); private: +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + static void HandleCslTimer(Timer &aTimer); + void HandleCslTimer(void); + uint16_t GetCslPhase(void) const; +#endif + enum { kMinBE = 3, ///< macMinBE (IEEE 802.15.4-2006). @@ -431,6 +512,12 @@ private: kStateCsmaBackoff, ///< CSMA backoff before transmission. kStateTransmit, ///< Radio is transmitting. kStateEnergyScan, ///< Energy scan. +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + kStateCslTransmit, ///< CSL transmission. +#endif +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + kStateCslSample, ///< CSL receive. +#endif }; bool RadioSupportsCsmaBackoff(void) const @@ -489,6 +576,35 @@ private: #else TimerMilli mTimer; #endif + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + /** + * The SSED sample window in units of 10 symbols. + * + */ + enum : uint32_t{ + kCslSampleWindow = OPENTHREAD_CONFIG_CSL_SAMPLE_WINDOW * kUsPerTenSymbols, + }; + + /** + * Csl state, always updated by `mCslTimer`. + * + */ + enum CslState : uint8_t{ + kCslIdle = 0, ///< CSL receiver is not started. + kCslSample, ///< Sampling CSL channel. + kCslSleep, ///< Radio in sleep. + }; + + uint32_t mCslTimeout; ///< The CSL synchronized timeout in seconds. + TimeMicro mCslSampleTime; ///< The CSL sample time of the current period. + uint16_t mCslPeriod; ///< The CSL sample period, in units of 10 symbols (160 microseconds). + uint8_t mCslChannel; ///< The CSL sample channel. + + CslState mCslState; + + TimerMicro mCslTimer; +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE }; /** diff --git a/src/core/radio/radio.hpp b/src/core/radio/radio.hpp index f014e6fb6..0e618640f 100644 --- a/src/core/radio/radio.hpp +++ b/src/core/radio/radio.hpp @@ -44,6 +44,16 @@ namespace ot { +enum +{ + kUsPerTenSymbols = OT_US_PER_TEN_SYMBOLS, ///< The microseconds per 10 symbols. +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + kMinCslPeriod = OPENTHREAD_CONFIG_MAC_CSL_MIN_PERIOD * 1000 / + kUsPerTenSymbols, ///< Minimum CSL period supported in units of 10 symbols. + kMaxCslTimeout = OPENTHREAD_CONFIG_MAC_CSL_MAX_TIMEOUT +#endif +}; + /** * @addtogroup core-radio * @@ -403,6 +413,30 @@ public: */ otError Receive(uint8_t aChannel) { return otPlatRadioReceive(GetInstance(), aChannel); } + /** + * This method updates the csl sample time in radio. + * + * @param[in] aCslSampleTime The csl sample time. + * + */ + void UpdateCslSampleTime(uint32_t aCslSampleTime) { otPlatRadioUpdateCslSampleTime(GetInstance(), aCslSampleTime); } + + /** This method enables csl sampling in radio. + * + * @param[in] aCslPeriod CSL period, 0 for disabling CSL. + * @param[in] aExtAddr The extended source address of CSL receiver's parent device (when the platforms + * generate enhanced ack, platforms may need to know acks to which address should include CSL IE). + * + * @retval OT_ERROR_NOT_SUPPORTED Radio driver doesn't support CSL. + * @retval OT_ERROR_FAILED Other platform specific errors. + * @retval OT_ERROR_NONE Successfully enabled or disabled CSL. + * + */ + otError EnableCsl(uint32_t aCslPeriod, const otExtAddress *aExtAddr) + { + return otPlatRadioEnableCsl(GetInstance(), aCslPeriod, aExtAddr); + } + /** * This method gets the radio transmit frame buffer. * diff --git a/src/core/thread/csl_tx_scheduler.cpp b/src/core/thread/csl_tx_scheduler.cpp new file mode 100644 index 000000000..62a695085 --- /dev/null +++ b/src/core/thread/csl_tx_scheduler.cpp @@ -0,0 +1,269 @@ +/* + * 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_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + +#include "common/locator-getters.hpp" +#include "common/logging.hpp" +#include "common/time.hpp" +#include "mac/mac.hpp" + +namespace ot { + +CslTxScheduler::Callbacks::Callbacks(Instance &aInstance) + : InstanceLocator(aInstance) +{ +} + +inline otError CslTxScheduler::Callbacks::PrepareFrameForChild(Mac::TxFrame &aFrame, + FrameContext &aContext, + Child & aChild) +{ + return Get().PrepareFrameForChild(aFrame, aContext, aChild); +} + +inline void CslTxScheduler::Callbacks::HandleSentFrameToChild(const Mac::TxFrame &aFrame, + const FrameContext &aContext, + otError aError, + Child & aChild) +{ + Get().HandleSentFrameToChild(aFrame, aContext, aError, aChild); +} + +//--------------------------------------------------------- +// +CslTxScheduler::CslTxScheduler(Instance &aInstance) + : InstanceLocator(aInstance) + , mCslTxChild(nullptr) + , mCslTxMessage(nullptr) + , mFrameContext() + , mCallbacks(aInstance) +{ +} + +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().Iterate(Child::kInStateAnyExceptInvalid)) + { + child.SetCslTxAttempts(0); + 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 request `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) +{ + uint64_t radioNow = otPlatRadioGetNow(&GetInstance()); + uint32_t minDelayTime = TimeMicro::kMaxDuration; + Child * bestChild = nullptr; + + for (Child &child : Get().Iterate(Child::kInStateAnyExceptInvalid)) + { + uint32_t delay; + + if (!child.IsCslSynchronized() || child.GetIndirectMessageCount() == 0 || + child.GetCslTxAttempts() >= kMaxCslTriggeredTxAttempts) + { + continue; + } + + delay = GetNextCslTransmissionDelay(child, radioNow); + if (delay < minDelayTime) + { + minDelayTime = delay; + bestChild = &child; + } + } + + if (bestChild != nullptr) + { + Get().RequestCslFrameTransmission(minDelayTime / 1000UL); + } + + mCslTxChild = bestChild; +} + +uint32_t CslTxScheduler::GetNextCslTransmissionDelay(const Child &aChild, uint64_t aRadioNow) +{ + uint32_t delay; + uint16_t period_offset = (aRadioNow / kUsPerTenSymbols) % aChild.GetCslPeriod(); + + if (aChild.GetCslPhase() > period_offset + kCslFrameRequestAheadThreshold) + { + delay = static_cast(aChild.GetCslPhase() - period_offset - kCslFrameRequestAheadThreshold) * + kUsPerTenSymbols; + } + else + { + delay = static_cast(aChild.GetCslPeriod() + aChild.GetCslPhase() - period_offset - + kCslFrameRequestAheadThreshold) * + kUsPerTenSymbols; + } + + return delay; +} + +otError CslTxScheduler::HandleFrameRequest(Mac::TxFrame &aFrame) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(mCslTxChild != nullptr, error = OT_ERROR_ABORT); + + SuccessOrExit(error = mCallbacks.PrepareFrameForChild(aFrame, mFrameContext, *mCslTxChild)); + mCslTxMessage = mCslTxChild->GetIndirectMessage(); + + 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. + + aFrame.SetIsARetransmission(true); + aFrame.SetSequence(mCslTxChild->GetIndirectDataSequenceNumber()); + + if (aFrame.GetSecurityEnabled()) + { + aFrame.SetFrameCounter(mCslTxChild->GetIndirectFrameCounter()); + aFrame.SetKeyId(mCslTxChild->GetIndirectKeyId()); + } + } + else + { + aFrame.SetIsARetransmission(false); + } + + aFrame.SetChannel(mCslTxChild->GetCslChannel()); + aFrame.SetTxPhase(mCslTxChild->GetCslPhase()); + aFrame.SetTxPeriod(mCslTxChild->GetCslPeriod()); + +exit: + return error; +} + +void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, otError aError) +{ + Child *child = mCslTxChild; + + VerifyOrExit(child != nullptr, OT_NOOP); // The result is no longer interested by upper layer + + mCslTxChild = nullptr; + mCslTxMessage = nullptr; + + HandleSentFrame(aFrame, aError, *child); + +exit: + return; +} + +void CslTxScheduler::HandleSentFrame(const Mac::TxFrame &aFrame, otError aError, Child &aChild) +{ + switch (aError) + { + case OT_ERROR_NONE: + aChild.ResetCslTxAttempts(); + aChild.ResetIndirectTxAttempts(); + break; + case OT_ERROR_NO_ACK: + aChild.IncrementCslTxAttempts(); + + otLogInfoMac("Csl tx to child %04x failed, attempt %d/%d", aChild.GetRloc16(), aChild.GetCslTxAttempts(), + kMaxCslTriggeredTxAttempts); + + // Fall through + case OT_ERROR_CHANNEL_ACCESS_FAILURE: + case OT_ERROR_ABORT: + + // 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()) + { + 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_CONFIG_MAC_CSL_TRANSMITTER_ENABLE diff --git a/src/core/thread/csl_tx_scheduler.hpp b/src/core/thread/csl_tx_scheduler.hpp new file mode 100644 index 000000000..b5c6c0fef --- /dev/null +++ b/src/core/thread/csl_tx_scheduler.hpp @@ -0,0 +1,215 @@ +/* + * 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. + */ + +#ifndef CSL_TX_SCHEDULER_HPP_ +#define CSL_TX_SCHEDULER_HPP_ + +#include "openthread-core-config.h" + +#include "common/locator.hpp" +#include "common/message.hpp" +#include "common/time.hpp" +#include "mac/mac.hpp" +#include "mac/mac_frame.hpp" +#include "thread/indirect_sender_frame_context.hpp" + +namespace ot { + +/** + * @addtogroup core-mesh-forwarding + * + * @brief + * This module includes definitions for CSL transmission scheduling. + * + * @{ + */ + +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + +class Child; + +/** + * This class implements CSL tx scheduling functionality. + * + */ +class CslTxScheduler : public InstanceLocator +{ + friend class Mac::Mac; + friend class IndirectSender; + +public: + enum + { + kMaxCslTriggeredTxAttempts = OPENTHREAD_CONFIG_MAC_MAX_TX_ATTEMPTS_INDIRECT_POLLS, + kCslFrameRequestAheadThreshold = 2000 / kUsPerTenSymbols, + }; + + /** + * This class defines all the child info required for scheduling CSL transmissions. + * + * `Child` class publicly inherits from this class. + * + */ + class ChildInfo + { + public: + uint8_t GetCslTxAttempts(void) const { return mCslTxAttempts; } + void SetCslTxAttempts(uint8_t aCslTxAttempts) { mCslTxAttempts = aCslTxAttempts; } + void IncrementCslTxAttempts(void) { mCslTxAttempts++; } + void ResetCslTxAttempts(void) { SetCslTxAttempts(0); } + + bool IsCslSynchronized(void) const { return mCslSynchronized && mCslPeriod > 0; } + void SetCslSynchronized(bool aCslSynchronized) { mCslSynchronized = aCslSynchronized; } + + uint8_t GetCslChannel(void) const { return mCslChannel; } + void SetCslChannel(uint8_t aChannel) { mCslChannel = aChannel; } + + uint32_t GetCslTimeout(void) const { return mCslTimeout; } + void SetCslTimeout(uint32_t aTimeout) { mCslTimeout = aTimeout; } + + uint16_t GetCslPeriod(void) const { return mCslPeriod; } + void SetCslPeriod(uint16_t aPeriod) { mCslPeriod = aPeriod; } + + uint16_t GetCslPhase(void) const { return mCslPhase; } + void SetCslPhase(uint16_t aPhase) { mCslPhase = aPhase; } + + TimeMilli GetCslLastHeard(void) const { return mCslLastHeard; } + void SetCslLastHeard(TimeMilli aCslLastHeard) { mCslLastHeard = aCslLastHeard; } + + private: + uint8_t mCslTxAttempts : 7; ///< Number of csl triggered tx attempts. + bool mCslSynchronized : 1; ///< Indicates whether or not the child is CSL synchronized. + uint8_t mCslChannel; ///< The channel the device will listen on. + uint32_t mCslTimeout; ///< The sync timeout, in seconds. + uint16_t mCslPeriod; ///< 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 heard. + + static_assert(kMaxCslTriggeredTxAttempts < (1 << 7), "mCslTxAttempts cannot fit max!"); + }; + + /** + * This class defines the callbacks used by the `CslTxScheduler`. + * + */ + class Callbacks : public InstanceLocator + { + friend class CslTxScheduler; + + private: + typedef IndirectSenderBase::FrameContext FrameContext; + + /** + * This constructor initializes the callbacks object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit Callbacks(Instance &aInstance); + + /** + * This callback method requests a frame to be prepared for CSL transmission to a given SSED. + * + * @param[out] aFrame A reference to a MAC frame where the new frame would be placed. + * @param[out] aContext A reference to a `FrameContext` where the context for the new frame would be placed. + * @param[in] aChild The child for which to prepare the frame. + * + * @retval OT_ERROR_NONE Frame was prepared successfully. + * @retval OT_ERROR_ABORT CSL transmission should be aborted (no frame for the child). + * + */ + otError PrepareFrameForChild(Mac::TxFrame &aFrame, FrameContext &aContext, Child &aChild); + + /** + * This callback method notifies the end of CSL frame transmission to a child. + * + * @param[in] aFrame The transmitted frame. + * @param[in] aContext The context associated with the frame when it was prepared. + * @param[in] aError OT_ERROR_NONE when the frame was transmitted successfully, + * OT_ERROR_NO_ACK when the frame was transmitted but no ACK was received, + * OT_ERROR_CHANNEL_ACCESS_FAILURE tx failed due to activity on the channel, + * OT_ERROR_ABORT when transmission was aborted for other reasons. + * @param[in] aChild The child to which the frame was transmitted. + * + */ + void HandleSentFrameToChild(const Mac::TxFrame &aFrame, + const FrameContext &aContext, + otError aError, + Child & aChild); + }; + /** + * This constructor initializes the csl tx scheduler object. + * + * @param[in] aInstance A reference to the OpenThread instance. + * + */ + explicit CslTxScheduler(Instance &aInstance); + + /** + * This method updates the next CSL transmission (finds the nearest child). + * + * 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 `mCslTxChild` to `nullptr` to notify the `HandleTransmitDone` + * that the operation has been aborted. + * + */ + void Update(void); + + /** + * This method clears all the states inside `CslTxScheduler` and the related states in each child. + * + */ + void Clear(void); + +private: + void RescheduleCslTx(void); + + uint32_t GetNextCslTransmissionDelay(const Child &aChild, uint64_t aRadioNow); + + // Callbacks from `Mac` + otError HandleFrameRequest(Mac::TxFrame &aFrame); + void HandleSentFrame(const Mac::TxFrame &aFrame, otError aError); + + void HandleSentFrame(const Mac::TxFrame &aFrame, otError aError, Child &aChild); + + Child * mCslTxChild; + Message * mCslTxMessage; + Callbacks::FrameContext mFrameContext; + Callbacks mCallbacks; +}; + +#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + +/** + * @} + * + */ + +} // namespace ot + +#endif // CSL_TX_SCHEDULER_HPP_ diff --git a/src/core/thread/indirect_sender.cpp b/src/core/thread/indirect_sender.cpp index 4e006e12d..a6bc33421 100644 --- a/src/core/thread/indirect_sender.cpp +++ b/src/core/thread/indirect_sender.cpp @@ -65,6 +65,9 @@ IndirectSender::IndirectSender(Instance &aInstance) , mEnabled(false) , mSourceMatchController(aInstance) , mDataPollHandler(aInstance) +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + , mCslTxScheduler(aInstance) +#endif { } @@ -79,6 +82,9 @@ void IndirectSender::Stop(void) } mDataPollHandler.Clear(); +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + mCslTxScheduler.Clear(); +#endif exit: mEnabled = false; @@ -147,6 +153,9 @@ void IndirectSender::ClearAllMessagesForSleepyChild(Child &aChild) mSourceMatchController.ResetMessageCount(aChild); mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild); +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + mCslTxScheduler.Update(); +#endif exit: return; @@ -189,6 +198,9 @@ void IndirectSender::HandleChildModeChange(Child &aChild, Mle::DeviceMode aOldMo mSourceMatchController.ResetMessageCount(aChild); mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild); +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + mCslTxScheduler.Update(); +#endif } // Since the queuing delays for direct transmissions are expected to @@ -255,6 +267,9 @@ void IndirectSender::RequestMessageUpdate(Child &aChild) aChild.SetWaitingForMessageUpdate(true); mDataPollHandler.RequestFrameChange(DataPollHandler::kPurgeFrame, aChild); +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + mCslTxScheduler.Update(); +#endif ExitNow(); } @@ -284,6 +299,9 @@ void IndirectSender::RequestMessageUpdate(Child &aChild) aChild.SetWaitingForMessageUpdate(true); mDataPollHandler.RequestFrameChange(DataPollHandler::kReplaceFrame, aChild); +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + mCslTxScheduler.Update(); +#endif exit: return; @@ -312,6 +330,9 @@ void IndirectSender::UpdateIndirectMessage(Child &aChild) Mac::Address childAddress; mDataPollHandler.HandleNewFrame(aChild); +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + mCslTxScheduler.Update(); +#endif aChild.GetMacAddress(childAddress); Get().LogMessage(MeshForwarder::kMessagePrepareIndirect, *message, &childAddress, OT_ERROR_NONE); @@ -473,6 +494,9 @@ void IndirectSender::HandleSentFrameToChild(const Mac::TxFrame &aFrame, { aChild.SetIndirectFragmentOffset(nextOffset); mDataPollHandler.HandleNewFrame(aChild); +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + mCslTxScheduler.Update(); +#endif ExitNow(); } diff --git a/src/core/thread/indirect_sender.hpp b/src/core/thread/indirect_sender.hpp index d6fd63bcd..db2a01896 100644 --- a/src/core/thread/indirect_sender.hpp +++ b/src/core/thread/indirect_sender.hpp @@ -40,6 +40,7 @@ #include "common/message.hpp" #include "mac/data_poll_handler.hpp" #include "mac/mac_frame.hpp" +#include "thread/csl_tx_scheduler.hpp" #include "thread/indirect_sender_frame_context.hpp" #include "thread/mle_types.hpp" #include "thread/src_match_controller.hpp" @@ -65,6 +66,9 @@ class IndirectSender : public InstanceLocator, public IndirectSenderBase { friend class Instance; friend class DataPollHandler::Callbacks; +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + friend class CslTxScheduler::Callbacks; +#endif public: /** @@ -76,6 +80,8 @@ public: class ChildInfo { friend class IndirectSender; + friend class DataPollHandler; + friend class CslTxScheduler; friend class SourceMatchController; public: @@ -221,6 +227,9 @@ private: bool mEnabled; SourceMatchController mSourceMatchController; DataPollHandler mDataPollHandler; +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + CslTxScheduler mCslTxScheduler; +#endif }; /** diff --git a/src/core/thread/indirect_sender_frame_context.hpp b/src/core/thread/indirect_sender_frame_context.hpp index 3d61dd767..13d0c0d43 100644 --- a/src/core/thread/indirect_sender_frame_context.hpp +++ b/src/core/thread/indirect_sender_frame_context.hpp @@ -73,6 +73,7 @@ public: struct FrameContext { friend class IndirectSender; + friend class CslTxScheduler; 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 d574cc84c..93a15d951 100644 --- a/src/core/thread/mesh_forwarder.cpp +++ b/src/core/thread/mesh_forwarder.cpp @@ -434,7 +434,12 @@ otError MeshForwarder::HandleFrameRequest(Mac::TxFrame &aFrame) { SuccessOrExit(error = Get().PrepareDiscoveryRequestFrame(aFrame)); } - +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (Get().IsCslEnabled() && mSendMessage->IsSubTypeMle()) + { + mSendMessage->SetLinkSecurityEnabled(true); + } +#endif mMessageNextOffset = PrepareDataFrame(aFrame, *mSendMessage, mMacSource, mMacDest, mAddMeshHeader, mMeshSource, mMeshDest); @@ -508,7 +513,7 @@ start: // Initialize MAC header fcf = Mac::Frame::kFcfFrameData; - Get().UpdateFrameControlField(aMessage.IsTimeSync(), fcf); + Get().UpdateFrameControlField(Get().FindNeighbor(aMacDest), aMessage.IsTimeSync(), fcf); fcf |= (aMacDest.IsShort()) ? Mac::Frame::kFcfDstAddrShort : Mac::Frame::kFcfDstAddrExt; fcf |= (aMacSource.IsShort()) ? Mac::Frame::kFcfSrcAddrShort : Mac::Frame::kFcfSrcAddrExt; diff --git a/src/core/thread/mesh_forwarder_ftd.cpp b/src/core/thread/mesh_forwarder_ftd.cpp index 42da6154b..ab8aee021 100644 --- a/src/core/thread/mesh_forwarder_ftd.cpp +++ b/src/core/thread/mesh_forwarder_ftd.cpp @@ -346,13 +346,28 @@ void MeshForwarder::SendMesh(Message &aMessage, Mac::TxFrame &aFrame) // initialize MAC header fcf = Mac::Frame::kFcfFrameData | Mac::Frame::kFcfPanidCompression | Mac::Frame::kFcfDstAddrShort | Mac::Frame::kFcfSrcAddrShort | Mac::Frame::kFcfAckRequest | Mac::Frame::kFcfSecurityEnabled; - Get().UpdateFrameControlField(aMessage.IsTimeSync(), fcf); + Get().UpdateFrameControlField(nullptr, aMessage.IsTimeSync(), fcf); aFrame.InitMacHeader(fcf, Mac::Frame::kKeyIdMode1 | Mac::Frame::kSecEncMic32); aFrame.SetDstPanId(Get().GetPanId()); aFrame.SetDstAddr(mMacDest.GetShort()); aFrame.SetSrcAddr(mMacSource.GetShort()); +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (Get().IsCslEnabled()) + { + Mac::HeaderIe ieList[2]; // CSL + Termination + + ieList[0].Init(); + ieList[0].SetId(Mac::Frame::kHeaderIeCsl); + ieList[0].SetLength(sizeof(Mac::CslIe)); + ieList[1].Init(); + ieList[1].SetId(Mac::Frame::kHeaderIeTermination2); + ieList[1].SetLength(0); + IgnoreError(aFrame.AppendHeaderIe(ieList, 2)); + } +#endif + // write payload OT_ASSERT(aMessage.GetLength() <= aFrame.GetMaxPayloadLength()); aMessage.Read(0, aMessage.GetLength(), aFrame.GetPayload()); diff --git a/src/core/thread/mle.cpp b/src/core/thread/mle.cpp index 8e866f143..e14a12c21 100644 --- a/src/core/thread/mle.cpp +++ b/src/core/thread/mle.cpp @@ -174,6 +174,12 @@ exit: return error; } +void Mle::ScheduleChildUpdateRequest(void) +{ + mChildUpdateRequestState = kChildUpdateRequestPending; + ScheduleMessageTransmissionTimer(); +} + otError Mle::Disable(void) { otError error = OT_ERROR_NONE; @@ -676,6 +682,13 @@ void Mle::SetStateChild(uint16_t aRloc16) InformPreviousParent(); mPreviousParentRloc = mParent.GetRloc16(); #endif + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (Get().IsCslEnabled()) + { + ScheduleChildUpdateRequest(); + } +#endif } void Mle::InformPreviousChannel(void) @@ -1376,6 +1389,32 @@ exit: return error; } +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +otError Mle::AppendCslChannel(Message &aMessage) +{ + otError error = OT_ERROR_NONE; + CslChannelTlv cslChannel; + + VerifyOrExit(Get().GetPanChannel() != Get().GetCslChannel(), OT_NOOP); + + cslChannel.Init(); + cslChannel.SetChannelPage(0); + cslChannel.SetChannel(Get().GetCslChannel()); + + SuccessOrExit(error = aMessage.Append(&cslChannel, sizeof(CslChannelTlv))); + +exit: + return error; +} + +otError Mle::AppendCslTimeout(Message &aMessage) +{ + OT_ASSERT(Get().IsCslEnabled()); + return Tlv::AppendUint32Tlv(aMessage, Tlv::kCslTimeout, + Get().GetCslTimeout() == 0 ? mTimeout : Get().GetCslTimeout()); +} +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + void Mle::HandleNotifierEvents(Events aEvents) { VerifyOrExit(!IsDisabled(), OT_NOOP); @@ -2066,7 +2105,18 @@ void Mle::ScheduleMessageTransmissionTimer(void) ExitNow(interval = kChildUpdateRequestPendingDelay); case kChildUpdateRequestActive: - ExitNow(interval = kUnicastRetransmissionDelay); +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + // CSL transmitter may respond in next CSL cycle. + if (Get().IsCslEnabled()) + { + ExitNow(interval = Get().GetCslPeriod() * kUsPerTenSymbols / 1000 + + static_cast(kUnicastRetransmissionDelay)); + } + else +#endif + { + ExitNow(interval = kUnicastRetransmissionDelay); + } } switch (mDataRequestState) @@ -2195,6 +2245,13 @@ otError Mle::SendChildUpdateRequest(void) SuccessOrExit(error = AppendSourceAddress(*message)); SuccessOrExit(error = AppendLeaderData(*message)); SuccessOrExit(error = AppendTimeout(*message, mTimeout)); +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (Get().IsCslEnabled()) + { + SuccessOrExit(error = AppendCslChannel(*message)); + SuccessOrExit(error = AppendCslTimeout(*message)); + } +#endif break; case kRoleDisabled: @@ -2216,7 +2273,11 @@ otError Mle::SendChildUpdateRequest(void) if (!IsRxOnWhenIdle()) { +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + Get().SetAttachMode(!Get().IsCslEnabled()); +#else Get().SetAttachMode(true); +#endif Get().SetRxOnWhenIdle(false); } else diff --git a/src/core/thread/mle.hpp b/src/core/thread/mle.hpp index 05d7c39e1..488adc4ac 100644 --- a/src/core/thread/mle.hpp +++ b/src/core/thread/mle.hpp @@ -918,6 +918,12 @@ public: otError GetLocatorAddress(Ip6::Address &aAddress, uint16_t aLocator) const; /** + * This method schedules a Child Update Request. + * + */ + void ScheduleChildUpdateRequest(void); + + /* * This method indicates whether or not the device has restored the network information from * non-volatile settings after boot. * @@ -1349,6 +1355,30 @@ protected: otError AppendXtalAccuracy(Message &aMessage); #endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + /** + * This method appends a CSL Channel TLV to a message. + * + * @param[in] aMessage A reference to the message. + * + * @retval OT_ERROR_NONE Successfully appended the CSL Channel TLV. + * @retval OT_ERROR_NO_BUFS Insufficient buffers available to append the CSL Channel TLV. + * + */ + otError AppendCslChannel(Message &aMessage); + + /** + * This method appends a CSL Sync Timeout TLV to a message. + * + * @param[in] aMessage A reference to the message. + * + * @retval OT_ERROR_NONE Successfully appended the CSL Timeout TLV. + * @retval OT_ERROR_NO_BUFS Insufficient buffers available to append the CSL Timeout TLV. + * + */ + otError AppendCslTimeout(Message &aMessage); +#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + /** * This method appends a Active Timestamp TLV to a message. * diff --git a/src/core/thread/mle_router.cpp b/src/core/thread/mle_router.cpp index c8b362caf..b01523008 100644 --- a/src/core/thread/mle_router.cpp +++ b/src/core/thread/mle_router.cpp @@ -1853,6 +1853,16 @@ void MleRouter::HandleStateUpdateTimer(void) OT_UNREACHABLE_CODE(break); } +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + if (child.IsCslSynchronized() && + TimerMilli::GetNow() - child.GetCslLastHeard() >= Time::SecToMsec(child.GetCslTimeout())) + { + otLogInfoMle("Child CSL synchronization expired"); + child.SetCslSynchronized(false); + Get().Update(); + } +#endif + if (TimerMilli::GetNow() - child.GetLastHeard() >= timeout) { otLogInfoMle("Child timeout expired"); @@ -2523,6 +2533,28 @@ void MleRouter::HandleChildUpdateRequest(const Message & aMessage, ExitNow(error = OT_ERROR_PARSE); } +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + if (child->IsCslSynchronized()) + { + CslChannelTlv cslChannel; + uint32_t cslTimeout; + + if (Tlv::FindUint32Tlv(aMessage, Tlv::kCslTimeout, cslTimeout) == OT_ERROR_NONE) + { + child->SetCslTimeout(cslTimeout); + } + + if (Tlv::FindTlv(aMessage, Tlv::kCslChannel, sizeof(cslChannel), cslChannel) == OT_ERROR_NONE) + { + child->SetCslChannel(static_cast(cslChannel.GetChannel())); + } + else + { + child->SetCslChannel(Get().GetPanChannel()); + } + } +#endif // OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + child->SetLastHeard(TimerMilli::GetNow()); if (oldMode != child->GetDeviceMode()) diff --git a/src/core/thread/mle_tlvs.hpp b/src/core/thread/mle_tlvs.hpp index 7285a0c17..4bdb5e233 100644 --- a/src/core/thread/mle_tlvs.hpp +++ b/src/core/thread/mle_tlvs.hpp @@ -101,6 +101,8 @@ public: kActiveDataset = 24, ///< Active Operational Dataset TLV kPendingDataset = 25, ///< Pending Operational Dataset TLV kDiscovery = 26, ///< Thread Discovery TLV + kCslChannel = 80, ///< CSL Channel TLV + kCslTimeout = 85, ///< CSL Timeout TLV /** * Applicable/Required only when time synchronization service @@ -1196,6 +1198,73 @@ public: bool IsValid(void) const { return GetLength() >= sizeof(*this) - sizeof(Tlv); } } OT_TOOL_PACKED_END; +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +/** + * This class implements CSL Channel TLV generation and parsing. + * + */ +OT_TOOL_PACKED_BEGIN +class CslChannelTlv : public Tlv +{ +public: + /** + * This method initializes the TLV. + * + */ + void Init(void) + { + SetType(kCslChannel); + SetLength(sizeof(*this) - sizeof(Tlv)); + } + + /** + * This method indicates whether or not the TLV appears to be well-formed. + * + * @retval TRUE If the TLV appears to be well-formed. + * @retval FALSE If the TLV does not appear to be well-formed. + * + */ + bool IsValid(void) const { return GetLength() == sizeof(*this) - sizeof(Tlv); } + + /** + * This method returns the Channel Page value. + * + * @returns The Channel Page value. + * + */ + uint8_t GetChannelPage(void) const { return mChannelPage; } + + /** + * This method sets the Channel Page value. + * + * @param[in] aChannelPage The Channel Page value. + * + */ + void SetChannelPage(uint8_t aChannelPage) { mChannelPage = aChannelPage; } + + /** + * This method returns the Channel value. + * + * @returns The Channel value. + * + */ + uint16_t GetChannel(void) const { return HostSwap16(mChannel); } + + /** + * This method sets the Channel value. + * + * @param[in] aChannel The Channel value. + * + */ + void SetChannel(uint16_t aChannel) { mChannel = HostSwap16(aChannel); } + +private: + uint8_t mChannelPage; + uint16_t mChannel; +} OT_TOOL_PACKED_END; + +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + /** * @} * diff --git a/src/core/thread/topology.hpp b/src/core/thread/topology.hpp index 218334c7c..c1d2bba5d 100644 --- a/src/core/thread/topology.hpp +++ b/src/core/thread/topology.hpp @@ -45,6 +45,7 @@ #include "common/timer.hpp" #include "mac/mac_types.hpp" #include "net/ip6.hpp" +#include "thread/csl_tx_scheduler.hpp" #include "thread/indirect_sender.hpp" #include "thread/link_quality.hpp" #include "thread/mle_tlvs.hpp" @@ -614,7 +615,13 @@ private: * This class represents a Thread Child. * */ -class Child : public Neighbor, public IndirectSender::ChildInfo, public DataPollHandler::ChildInfo +class Child : public Neighbor, + public IndirectSender::ChildInfo, + public DataPollHandler::ChildInfo +#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + , + public CslTxScheduler::ChildInfo +#endif { class AddressIteratorBuilder; diff --git a/tests/scripts/thread-cert/config.py b/tests/scripts/thread-cert/config.py index 9823d233a..c90bd6ffe 100644 --- a/tests/scripts/thread-cert/config.py +++ b/tests/scripts/thread-cert/config.py @@ -229,6 +229,7 @@ def create_default_mle_tlvs_factories(): mle.TlvType.PANID: mle.PanIdFactory(), mle.TlvType.ACTIVE_TIMESTAMP: mle.ActiveTimestampFactory(), mle.TlvType.PENDING_TIMESTAMP: mle.PendingTimestampFactory(), + mle.TlvType.CSL_SYNCHRONIZED_TIMEOUT: mle.CslSynchronizedTimeoutFactory(), mle.TlvType.ACTIVE_OPERATIONAL_DATASET: mle.ActiveOperationalDatasetFactory(), mle.TlvType.PENDING_OPERATIONAL_DATASET: mle.PendingOperationalDatasetFactory(), mle.TlvType.TIME_REQUEST: mle.TimeRequestFactory(), diff --git a/tests/scripts/thread-cert/mesh_cop.py b/tests/scripts/thread-cert/mesh_cop.py index e6e362f33..71eb25d84 100644 --- a/tests/scripts/thread-cert/mesh_cop.py +++ b/tests/scripts/thread-cert/mesh_cop.py @@ -76,6 +76,7 @@ class TlvType(IntEnum): PERIOD = 55 SCAN_DURATION = 56 ENERGY_LIST = 57 + CSL_SYNCHRONIZED_TIMEOUT = 85 DISCOVERY_REQUEST = 128 DISCOVERY_RESPONSE = 129 diff --git a/tests/scripts/thread-cert/mle.py b/tests/scripts/thread-cert/mle.py index 6dd76034b..768cc23b6 100644 --- a/tests/scripts/thread-cert/mle.py +++ b/tests/scripts/thread-cert/mle.py @@ -87,6 +87,7 @@ class TlvType(IntEnum): ACTIVE_OPERATIONAL_DATASET = 24 PENDING_OPERATIONAL_DATASET = 25 THREAD_DISCOVERY = 26 + CSL_SYNCHRONIZED_TIMEOUT = 85 TIME_REQUEST = 252 TIME_PARAMETER = 253 @@ -1047,6 +1048,19 @@ class ThreadDiscoveryFactory: return ThreadDiscovery(tlvs) +class CslSynchronizedTimeout: + # TODO: Not implemented yet + + def __init__(self): + print("CslSynchronizedTimeout is not implemented yet.") + + +class CslSynchronizedTimeoutFactory: + + def parse(self, data, message_info): + return CslSynchronizedTimeout() + + class TimeRequest: # TODO: Not implemented yet diff --git a/tests/scripts/thread-cert/node.py b/tests/scripts/thread-cert/node.py index d80941e88..62f7b9bfc 100755 --- a/tests/scripts/thread-cert/node.py +++ b/tests/scripts/thread-cert/node.py @@ -660,6 +660,22 @@ class Node: self.send_command('pollperiod %d' % pollperiod) self._expect('Done') + def get_csl_info(self): + self.send_command('csl') + self._expect('Done') + + def set_csl_channel(self, csl_channel): + self.send_command('csl channel %d' % csl_channel) + self._expect('Done') + + def set_csl_period(self, csl_period): + self.send_command('csl period %d' % csl_period) + self._expect('Done') + + def set_csl_timeout(self, csl_timeout): + self.send_command('csl timeout %d' % csl_timeout) + self._expect('Done') + def set_router_upgrade_threshold(self, threshold): cmd = 'routerupgradethreshold %d' % threshold self.send_command(cmd) diff --git a/tests/scripts/thread-cert/v1_2_test_csl_transmission.py b/tests/scripts/thread-cert/v1_2_test_csl_transmission.py new file mode 100755 index 000000000..fd84a69d6 --- /dev/null +++ b/tests/scripts/thread-cert/v1_2_test_csl_transmission.py @@ -0,0 +1,85 @@ +#!/usr/bin/env python3 +# +# 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. +# + +import unittest + +import thread_cert + +LEADER = 1 +SSED_1 = 2 + +CSL_PERIOD = 500 * 6.25 # 500ms +CSL_TIMEOUT = 30 # 30s +CSL_CHANNEL = 12 + + +class SSED_CslTransmission(thread_cert.TestCase): + TOPOLOGY = { + LEADER: { + 'version': '1.2', + }, + SSED_1: { + 'version': '1.2', + 'mode': 's', + }, + } + """All nodes are created with default configurations""" + + def test(self): + + self.nodes[SSED_1].set_csl_period(CSL_PERIOD) + self.nodes[SSED_1].set_csl_timeout(CSL_TIMEOUT) + self.nodes[SSED_1].set_csl_channel(CSL_CHANNEL) + + self.nodes[SSED_1].get_csl_info() + + self.nodes[LEADER].start() + self.simulator.go(5) + self.assertEqual(self.nodes[LEADER].get_state(), 'leader') + + self.nodes[SSED_1].start() + self.simulator.go(7) + self.assertEqual(self.nodes[SSED_1].get_state(), 'child') + + print('SSED rloc:%s' % self.nodes[SSED_1].get_rloc()) + self.assertTrue(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc())) + self.simulator.go(5) + + self.nodes[SSED_1].set_csl_period(0) + self.assertFalse(self.nodes[LEADER].ping(self.nodes[SSED_1].get_rloc())) + self.simulator.go(2) + + self.nodes[SSED_1].set_pollperiod(1000) + self.simulator.go(2) + self.nodes[SSED_1].set_pollperiod(0) + self.simulator.go(5) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index 12edc30c9..0e2f8370b 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -592,20 +592,28 @@ void otPlatFlashWrite(otInstance *aInstance, uint8_t aSwapIndex, uint32_t aOffse } #if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE -uint64_t otPlatTimeGet(void) -{ - struct timeval tv; - - gettimeofday(&tv, nullptr); - - return (uint64_t)tv.tv_sec * 1000000 + (uint64_t)tv.tv_usec; -} - uint16_t otPlatTimeGetXtalAccuracy(void) { return 0; } -#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE +#endif + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +otError otPlatRadioEnableCsl(otInstance *aInstance, uint32_t aCslPeriod, const otExtAddress *aExtAddr) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aCslPeriod); + OT_UNUSED_VARIABLE(aExtAddr); + + return OT_ERROR_NONE; +} + +void otPlatRadioUpdateCslSampleTime(otInstance *aInstance, uint32_t aCslSampleTime) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aCslSampleTime); +} +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE #if OPENTHREAD_CONFIG_OTNS_ENABLE void otPlatOtnsStatus(const char *aStatus)