From 5e0f52e1fc786dc3621458c37d171666fe2bb032 Mon Sep 17 00:00:00 2001 From: Eduardo Montoya Date: Mon, 7 Oct 2024 08:21:22 +0200 Subject: [PATCH] [mac] enable wake-up frame periodic sniffing When `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE` is enabled: - APIs are available to configure and enable wake-up frames sniffing. - `SubMac::HandleWedTimer` periodically schedules receive slots on wake-up channel. - Wake-up frames are processed. - Upon reception of a wake-up frame, WUL is stopped. --- include/openthread/instance.h | 2 +- include/openthread/link.h | 70 ++++++++++++++ src/cli/README.md | 79 +++++++++++++++- src/cli/cli.cpp | 129 ++++++++++++++++++++++---- src/core/BUILD.gn | 3 + src/core/CMakeLists.txt | 2 + src/core/api/link_api.cpp | 32 +++++++ src/core/config/mac.h | 10 ++ src/core/config/wakeup.h | 18 ++++ src/core/mac/mac.cpp | 158 +++++++++++++++++++++++++++++++- src/core/mac/mac.hpp | 74 ++++++++++++++- src/core/mac/mac_links.hpp | 19 ++++ src/core/mac/sub_mac.cpp | 9 ++ src/core/mac/sub_mac.hpp | 35 ++++++- src/core/mac/sub_mac_wed.cpp | 91 ++++++++++++++++++ src/core/radio/radio.hpp | 46 ++++++---- src/core/thread/key_manager.cpp | 12 +++ src/core/thread/key_manager.hpp | 13 +++ src/core/thread/tmf.hpp | 1 - 19 files changed, 753 insertions(+), 50 deletions(-) create mode 100644 src/core/mac/sub_mac_wed.cpp diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 56d63bb35d67..70629db17e2c 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (452) +#define OPENTHREAD_API_VERSION (453) /** * @addtogroup api-instance diff --git a/include/openthread/link.h b/include/openthread/link.h index f0bce98e1116..21157ec98fdd 100644 --- a/include/openthread/link.h +++ b/include/openthread/link.h @@ -1126,6 +1126,76 @@ uint8_t otLinkGetWakeupChannel(otInstance *aInstance); */ otError otLinkSetWakeupChannel(otInstance *aInstance, uint8_t aChannel); +/** + * Enables or disables listening for wake-up frames. + * + * Requires `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aEnable true to enable listening for wake-up frames, or false otherwise. + * + * @retval OT_ERROR_NONE Successfully enabled / disabled the listening for wake-up frames. + * @retval OT_ERROR_INVALID_STATE Could not enable listening for wake-up frames due to bad configuration. + */ +otError otLinkSetWedListenEnabled(otInstance *aInstance, bool aEnable); + +/** + * Returns whether listening for wake-up frames is enabled. + * + * Requires `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @retval TRUE If listening for wake-up frames is enabled. + * @retval FALSE If listening for wake-up frames is not enabled. + */ +bool otLinkIsWedListenEnabled(otInstance *aInstance); + +/** + * Gets the WED listen interval in microseconds. + * + * Requires `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The WED listen interval in microseconds. + */ +uint32_t otLinkGetWedListenInterval(otInstance *aInstance); + +/** + * Sets the WED listen interval in microseconds. + * + * Requires `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aInterval The WED listen interval in microseconds. + */ +void otLinkSetWedListenInterval(otInstance *aInstance, uint32_t aInterval); + +/** + * Gets the WED listen duration. + * + * Requires `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The WED listen duration in microseconds. + */ +uint16_t otLinkGetWedListenDuration(otInstance *aInstance); + +/** + * Sets the WED listen duration in microseconds. + * + * Requires `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * @param[in] aDuration The WED listen duration in microseconds. + * + * @retval OT_ERROR_NONE Successfully set the WED listen duration. + * @retval OT_ERROR_INVALID_ARGS Invalid WED listen duration. + */ +otError otLinkSetWedListenDuration(otInstance *aInstance, uint16_t aDuration); + /** * @} */ diff --git a/src/cli/README.md b/src/cli/README.md index fbf037d96f8b..048373d307e2 100644 --- a/src/cli/README.md +++ b/src/cli/README.md @@ -133,7 +133,7 @@ Done - [vendor](#vendor-name) - [verhoeff](#verhoeff-calculate) - [version](#version) -- [wakeupchannel](#wakeupchannel) +- [wakeup](#wakeup-channel) ## OpenThread Command Details @@ -4393,25 +4393,94 @@ Factory Diagnostics module is enabled only when building OpenThread with `OPENTH [diag]: ../../src/core/diags/README.md -### wakeupchannel +### wakeup channel Get the wake-up channel. Requires `OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE` or `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. ```bash -> wakeupchannel +> wakeup channel 12 Done ``` -### wakeupchannel \ +### wakeup channel \ Set the wake-up channel. Requires `OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE` or `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. ```bash -> wakeupchannel 12 +> wakeup channel 12 +Done +``` + +### wakeup interval + +Get the wake-up listen interval. + +Requires `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. + +```bash +> wakeup interval +1000000 +Done +``` + +### wakeup interval \ + +Set the wake-up listen interval. + +Requires `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. + +```bash +> wakeup interval 1000000 +Done +``` + +### wakeup duration + +Get the wake-up listen duration. + +Requires `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. + +```bash +> wakeup duration +8000 +Done +``` + +### wakeup duration \ + +Set the wake-up listen duration. + +Requires `OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE`. + +```bash +> wakeup duration 8000 +Done +``` + +### wakeup listen + +Show the state of wake-up listening feature. + +`OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE` is required. + +```bash +> wakeup listen +Enabled +Done +``` + +### wakeup listen \[enable|disable\] + +Enable/disable listening for wake-up frames. + +`OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE` is required. + +```bash +> wakeup listen enable Done ``` diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index 9f1710b0ebe5..95adc8d59d6e 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -8199,25 +8199,113 @@ template <> otError Interpreter::Process(Arg aArgs[]) #endif // OPENTHREAD_CONFIG_VERHOEFF_CHECKSUM_ENABLE #if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE -/** - * @cli wakeupchannel (get,set) - * @code - * wakeupchannel - * 12 - * Done - * @endcode - * @code - * wakeupchannel 12 - * Done - * @endcode - * @cparam wakeupchannel [@ca{channel}] - * Use `channel` to set the wake-up channel. - * @par - * Gets or sets the wake-up channel value. - */ -template <> otError Interpreter::Process(Arg aArgs[]) +template <> otError Interpreter::Process(Arg aArgs[]) { - return ProcessGetSet(aArgs, otLinkGetWakeupChannel, otLinkSetWakeupChannel); + otError error = OT_ERROR_NONE; + + /** + * @cli wakeup channel (get,set) + * @code + * wakeup channel + * 12 + * Done + * @endcode + * @code + * wakeup channel 12 + * Done + * @endcode + * @cparam wakeup channel [@ca{channel}] + * Use `channel` to set the wake-up channel. + * @par + * Gets or sets the wake-up channel value. + * @sa otLinkGetWakeupChannel + * @sa otLinkSetWakeupChannel + */ + if (aArgs[0] == "channel") + { + error = ProcessGetSet(aArgs + 1, otLinkGetWakeupChannel, otLinkSetWakeupChannel); + } +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + /** + * @cli wakeup interval (get,set) + * @code + * wakeup interval + * 12 + * Done + * @endcode + * @code + * wakeup interval 1000000 + * Done + * @endcode + * @cparam wakeup interval @ca{interval} + * @par + * Gets or sets the wake-up interval value. + * @sa otLinkGetWedListenInterval + * @sa otLinkSetWedListenInterval + */ + else if (aArgs[0] == "interval") + { + error = ProcessGetSet(aArgs + 1, otLinkGetWedListenInterval, otLinkSetWedListenInterval); + } + /** + * @cli wakeup duration (get,set) + * @code + * wakeup duration + * 8000 + * Done + * @endcode + * @code + * wakeup duration 8000 + * Done + * @endcode + * @cparam wakeup duration @ca{duration} + * @par + * Gets or sets the wake-up duration value. + * @sa otLinkGetWedListenDuration + * @sa otLinkSetWedListenDuration + */ + else if (aArgs[0] == "duration") + { + error = ProcessGetSet(aArgs + 1, otLinkGetWedListenDuration, otLinkSetWedListenDuration); + } + /** + * @cli wakeup listen (enable,disable) + * @code + * wakeup listen + * disabled + * Done + * @endcode + * @code + * wakeup listen + * enabled + * Done + * @endcode + * @code + * wakeup listen enable + * Done + * @endcode + * @code + * wakeup listen disable + * Done + * @endcode + * @cparam wakeup listen @ca{enable} + * @par + * Gets or sets current wake-up listening link state. + * @sa otLinkIsWedListenEnabled + * @sa otLinkSetWedListenEnabled + */ + else if (aArgs[0] == "listen") + { + error = ProcessEnableDisable(aArgs + 1, otLinkIsWedListenEnabled, otLinkSetWedListenEnabled); + } +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + else + { + ExitNow(error = OT_ERROR_INVALID_ARGS); + } + +exit: + return error; } #endif // OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE @@ -8530,6 +8618,11 @@ otError Interpreter::ProcessCommand(Arg aArgs[]) #endif #endif // OPENTHREAD_FTD || OPENTHREAD_MTD CmdEntry("version"), +#if OPENTHREAD_FTD || OPENTHREAD_MTD +#if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + CmdEntry("wakeup"), +#endif +#endif }; #undef CmdEntry diff --git a/src/core/BUILD.gn b/src/core/BUILD.gn index ac5f933891ba..962a7b923de8 100644 --- a/src/core/BUILD.gn +++ b/src/core/BUILD.gn @@ -508,6 +508,7 @@ openthread_core_files = [ "mac/sub_mac.hpp", "mac/sub_mac_callbacks.cpp", "mac/sub_mac_csl_receiver.cpp", + "mac/sub_mac_wed.cpp", "meshcop/announce_begin_client.cpp", "meshcop/announce_begin_client.hpp", "meshcop/border_agent.cpp", @@ -777,6 +778,8 @@ openthread_radio_sources = [ "mac/mac_types.cpp", "mac/sub_mac.cpp", "mac/sub_mac_callbacks.cpp", + "mac/sub_mac_csl_receiver.cpp", + "mac/sub_mac_wed.cpp", "radio/radio.cpp", "radio/radio_callbacks.cpp", "radio/radio_platform.cpp", diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 0089c42ecf9a..26dec232b606 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -146,6 +146,7 @@ set(COMMON_SOURCES mac/sub_mac.cpp mac/sub_mac_callbacks.cpp mac/sub_mac_csl_receiver.cpp + mac/sub_mac_wed.cpp meshcop/announce_begin_client.cpp meshcop/border_agent.cpp meshcop/commissioner.cpp @@ -295,6 +296,7 @@ set(RADIO_COMMON_SOURCES mac/sub_mac.cpp mac/sub_mac_callbacks.cpp mac/sub_mac_csl_receiver.cpp + mac/sub_mac_wed.cpp radio/radio.cpp radio/radio_callbacks.cpp radio/radio_platform.cpp diff --git a/src/core/api/link_api.cpp b/src/core/api/link_api.cpp index 3935b7eee289..b0ddc15b9f50 100644 --- a/src/core/api/link_api.cpp +++ b/src/core/api/link_api.cpp @@ -504,3 +504,35 @@ otError otLinkGetRegion(otInstance *aInstance, uint16_t *aRegionCode) return error; } + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +otError otLinkSetWedListenEnabled(otInstance *aInstance, bool aEnable) +{ + return AsCoreType(aInstance).Get().SetWedListenEnabled(aEnable); +} + +bool otLinkIsWedListenEnabled(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().IsWedListenEnabled(); +} + +uint32_t otLinkGetWedListenInterval(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().GetWedListenInterval(); +} + +void otLinkSetWedListenInterval(otInstance *aInstance, uint32_t aInterval) +{ + return AsCoreType(aInstance).Get().SetWedListenInterval(aInterval); +} + +uint16_t otLinkGetWedListenDuration(otInstance *aInstance) +{ + return AsCoreType(aInstance).Get().GetWedListenDuration(); +} + +otError otLinkSetWedListenDuration(otInstance *aInstance, uint16_t aDuration) +{ + return AsCoreType(aInstance).Get().SetWedListenDuration(aDuration); +} +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE diff --git a/src/core/config/mac.h b/src/core/config/mac.h index d2359a91db24..4e3768a00834 100644 --- a/src/core/config/mac.h +++ b/src/core/config/mac.h @@ -497,6 +497,16 @@ #define OPENTHREAD_CONFIG_CSL_RECEIVE_TIME_AHEAD 320 #endif +/** + * @def OPENTHREAD_CONFIG_WED_RECEIVE_TIME_AFTER + * + * Margin to be applied after the end of a WED listen duration to schedule the next listen interval, in units of + * microseconds. + */ +#ifndef OPENTHREAD_CONFIG_WED_RECEIVE_TIME_AFTER +#define OPENTHREAD_CONFIG_WED_RECEIVE_TIME_AFTER 500 +#endif + /** * @def OPENTHREAD_CONFIG_MIN_RECEIVE_ON_AHEAD * diff --git a/src/core/config/wakeup.h b/src/core/config/wakeup.h index 0dd52386f8df..c9f37cccbbc9 100644 --- a/src/core/config/wakeup.h +++ b/src/core/config/wakeup.h @@ -63,6 +63,24 @@ #define OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_WED_LISTEN_INTERVAL + * + * The default WED listen interval in microseconds. + */ +#ifndef OPENTHREAD_CONFIG_WED_LISTEN_INTERVAL +#define OPENTHREAD_CONFIG_WED_LISTEN_INTERVAL 1000000 +#endif + +/** + * @def OPENTHREAD_CONFIG_WED_LISTEN_DURATION + * + * The default WED listen duration in microseconds. + */ +#ifndef OPENTHREAD_CONFIG_WED_LISTEN_DURATION +#define OPENTHREAD_CONFIG_WED_LISTEN_DURATION 8000 +#endif + /** * @} */ diff --git a/src/core/mac/mac.cpp b/src/core/mac/mac.cpp index ca9c28057b98..2637c5c164e4 100644 --- a/src/core/mac/mac.cpp +++ b/src/core/mac/mac.cpp @@ -59,6 +59,9 @@ Mac::Mac(Instance &aInstance) #if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS , mShouldDelaySleep(false) , mDelayingSleep(false) +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + , mWedListenEnabled(false) #endif , mOperation(kOperationIdle) , mPendingOperations(0) @@ -83,6 +86,10 @@ Mac::Mac(Instance &aInstance) , mCslPeriod(0) #endif , mWakeupChannel(OPENTHREAD_CONFIG_DEFAULT_WAKEUP_CHANNEL) +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + , mWedListenInterval(kDefaultWedListenInterval) + , mWedListenDuration(kDefaultWedListenDuration) +#endif , mActiveScanHandler(nullptr) // Initialize `mActiveScanHandler` and `mEnergyScanHandler` union , mScanHandlerContext(nullptr) , mLinks(aInstance) @@ -1591,8 +1598,27 @@ Error Mac::ProcessReceiveSecurity(RxFrame &aFrame, const Address &aSrcAddr, Neig break; case Frame::kKeyIdMode2: - macKey = &mMode2KeyMaterial; - extAddress = &AsCoreType(&sMode2ExtAddress); +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (aFrame.IsWakeupFrame()) + { + uint32_t sequence; + + // TODO: Avoid generating a new key if a wake-up frame was recently received already + + IgnoreError(aFrame.GetKeyId(keyid)); + sequence = BigEndian::ReadUint32(aFrame.GetKeySource()); + VerifyOrExit(((sequence & 0x7f) + 1) == keyid, error = kErrorSecurity); + + macKey = (sequence == keyManager.GetCurrentKeySequence()) ? mLinks.GetCurrentMacKey(aFrame) + : &keyManager.GetTemporaryMacKey(sequence); + extAddress = &aSrcAddr.GetExtended(); + } + else +#endif + { + macKey = &mMode2KeyMaterial; + extAddress = &AsCoreType(&sMode2ExtAddress); + } break; default: @@ -1974,6 +2000,12 @@ void Mac::HandleReceivedFrame(RxFrame *aFrame, Error aError) mCounters.mRxData++; break; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + case Frame::kTypeMultipurpose: + SuccessOrExit(error = HandleWakeupFrame(*aFrame)); + OT_FALL_THROUGH; +#endif + default: mCounters.mRxOther++; ExitNow(); @@ -2318,6 +2350,14 @@ void Mac::SetCslChannel(uint8_t aChannel) void Mac::SetCslPeriod(uint16_t aPeriod) { +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + if (IsWedListenEnabled() && aPeriod != 0) + { + IgnoreError(SetWedListenEnabled(false)); + LogWarn("Disabling wake-up frame listening due to CSL period change"); + } +#endif + mCslPeriod = aPeriod; UpdateCsl(); } @@ -2418,10 +2458,124 @@ Error Mac::SetWakeupChannel(uint8_t aChannel) VerifyOrExit(mSupportedChannelMask.ContainsChannel(aChannel), error = kErrorInvalidArgs); mWakeupChannel = aChannel; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + UpdateWakeupListening(); +#endif + +exit: + return error; +} +#endif + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +void Mac::SetWedListenInterval(uint32_t aInterval) +{ + mWedListenInterval = aInterval; + UpdateWakeupListening(); +} + +Error Mac::SetWedListenDuration(uint16_t aDuration) +{ + Error error = kErrorNone; + + VerifyOrExit(aDuration >= kMinWedListenDuration, error = kErrorInvalidArgs); + + mWedListenDuration = aDuration; + UpdateWakeupListening(); + +exit: + return error; +} + +Error Mac::SetWedListenEnabled(bool aEnable) +{ + Error error = kErrorNone; + + VerifyOrExit(GetWedListenInterval() > GetWedListenDuration(), error = kErrorInvalidArgs); + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + if (aEnable && GetCslPeriod() > 0) + { + LogWarn("Cannot enable wake-up frame listening while CSL is enabled"); + ExitNow(error = kErrorInvalidState); + } +#endif + + if (aEnable == mWedListenEnabled) + { + LogInfo("Listening for wake up frames was already %s", aEnable ? "started" : "stopped"); + ExitNow(); + } + + mWedListenEnabled = aEnable; + UpdateWakeupListening(); + + LogInfo("Listening for wake up frames %s: chan:%u, addr:%s", aEnable ? "started" : "stopped", mWakeupChannel, + GetExtAddress().ToString().AsCString()); + exit: return error; } + +void Mac::UpdateWakeupListening(void) +{ + uint32_t interval = mWedListenEnabled ? mWedListenInterval : 0; + uint16_t duration = mWedListenEnabled ? mWedListenDuration : 0; + uint8_t channel = mWakeupChannel ? mWakeupChannel : mPanChannel; + + mLinks.UpdateWakeupListening(interval, duration, channel); +} + +Error Mac::HandleWakeupFrame(const RxFrame &aFrame) +{ + Error error = kErrorNone; + const ConnectionIe *connectionIe; + uint32_t rvTimeUs; + uint64_t rvTimestampUs; + uint32_t attachDelayMs; + uint64_t radioNowUs; + uint8_t retryInterval; + uint8_t retryCount; + + VerifyOrExit(mWedListenEnabled && aFrame.IsWakeupFrame()); + connectionIe = aFrame.GetConnectionIe(); + retryInterval = connectionIe->GetRetryInterval(); + retryCount = connectionIe->GetRetryCount(); + VerifyOrExit(retryInterval > 0 && retryCount > 0, error = kErrorInvalidArgs); + + radioNowUs = otPlatRadioGetNow(&GetInstance()); + rvTimeUs = aFrame.GetRendezvousTimeIe()->GetRendezvousTime() * kUsPerTenSymbols; + rvTimestampUs = aFrame.GetTimestamp() + kRadioHeaderPhrDuration + aFrame.GetLength() * kOctetDuration + rvTimeUs; + if (rvTimestampUs > radioNowUs + OPENTHREAD_CONFIG_MAC_CSL_REQUEST_AHEAD_US) + { + attachDelayMs = static_cast(rvTimestampUs - radioNowUs - OPENTHREAD_CONFIG_MAC_CSL_REQUEST_AHEAD_US); + attachDelayMs = attachDelayMs / 1000; + } + else + { + attachDelayMs = 0; + } + +#if OT_SHOULD_LOG_AT(OT_LOG_LEVEL_INFO) + { + uint32_t frameCounter; + + IgnoreError(aFrame.GetFrameCounter(frameCounter)); + LogInfo("Received wake-up frame, fc:%lu, rendezvous:%luus, retries:%u/%u", ToUlong(frameCounter), + ToUlong(rvTimeUs), retryCount, retryInterval); + } #endif + // Stop receiving more wake up frames + IgnoreError(SetWedListenEnabled(false)); + + // TODO: start MLE attach process with the WC + OT_UNUSED_VARIABLE(attachDelayMs); + +exit: + return error; +} +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + } // namespace Mac } // namespace ot diff --git a/src/core/mac/mac.hpp b/src/core/mac/mac.hpp index 931e27006384..45bb70316d6b 100644 --- a/src/core/mac/mac.hpp +++ b/src/core/mac/mac.hpp @@ -89,6 +89,9 @@ constexpr uint8_t kTxNumBcast = OPENTHREAD_CONFIG_MAC_TX_NUM_BCAST; ///< Num of constexpr uint16_t kMinCslIePeriod = OPENTHREAD_CONFIG_MAC_CSL_MIN_PERIOD; +constexpr uint32_t kDefaultWedListenInterval = OPENTHREAD_CONFIG_WED_LISTEN_INTERVAL; +constexpr uint16_t kDefaultWedListenDuration = OPENTHREAD_CONFIG_WED_LISTEN_DURATION; + /** * Defines the function pointer called on receiving an IEEE 802.15.4 Beacon during an Active Scan. */ @@ -681,17 +684,17 @@ class Mac : public InstanceLocator, private NonCopyable Error GetRegion(uint16_t &aRegionCode) const; /** - * Gets the Wake-up channel. + * Gets the wake-up channel. * - * @returns Wake-up channel. + * @returns wake-up channel. */ uint8_t GetWakeupChannel(void) const { return mWakeupChannel; } #if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE /** - * Sets the Wake-up channel. + * Sets the wake-up channel. * - * @param[in] aChannel The Wake-up channel. + * @param[in] aChannel The wake-up channel. * * @retval kErrorNone Successfully set the wake-up channel. * @retval kErrorInvalidArgs The @p aChannel is not in the supported channel mask. @@ -699,6 +702,57 @@ class Mac : public InstanceLocator, private NonCopyable Error SetWakeupChannel(uint8_t aChannel); #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + /** + * Gets the WED listen interval. + * + * @returns WED listen interval in microseconds. + */ + uint32_t GetWedListenInterval(void) const { return mWedListenInterval; } + + /** + * Sets the WED listen interval. + * + * @param[in] aInterval The WED listen interval in microseconds. + */ + void SetWedListenInterval(uint32_t aInterval); + + /** + * Gets the WED listen duration. + * + * @returns WED listen duration in microseconds. + */ + uint16_t GetWedListenDuration(void) const { return mWedListenDuration; } + + /** + * Sets the WED listen duration. + * + * @param[in] aDuration The WED listen duration in microseconds. + * + * @retval kErrorNone Successfully set the WED listen duration. + * @retval kErrorInvalidArgs The @p aDuration is below the minimum supported. + */ + Error SetWedListenDuration(uint16_t aDuration); + + /** + * Enables/disables listening for wake-up frames. + * + * @param[in] aEnable TRUE to enable listening for wake-up frames, FALSE otherwise + * + * @retval kErrorNone Successfully enabled/disabled listening for wake-up frames. + * @retval kErrorInvalidState Could not enable/disable listening for wake-up frames. + */ + Error SetWedListenEnabled(bool aEnable); + + /** + * Returns whether listening for wake-up frames is enabled. + * + * @retval TRUE If listening for wake-up frames is enabled. + * @retval FALSE If listening for wake-up frames is not enabled. + */ + bool IsWedListenEnabled(void) const { return mWedListenEnabled; } +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + private: static constexpr uint16_t kMaxCcaSampleCount = OPENTHREAD_CONFIG_CCA_FAILURE_RATE_AVERAGING_WINDOW; @@ -786,6 +840,10 @@ class Mac : public InstanceLocator, private NonCopyable #endif #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE void ProcessEnhAckProbing(const RxFrame &aFrame, const Neighbor &aNeighbor); +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + Error HandleWakeupFrame(const RxFrame &aFrame); + void UpdateWakeupListening(void); #endif static const char *OperationToString(Operation aOperation); @@ -803,6 +861,9 @@ class Mac : public InstanceLocator, private NonCopyable #if OPENTHREAD_CONFIG_MAC_STAY_AWAKE_BETWEEN_FRAGMENTS bool mShouldDelaySleep : 1; bool mDelayingSleep : 1; +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + bool mWedListenEnabled : 1; #endif Operation mOperation; uint16_t mPendingOperations; @@ -829,7 +890,10 @@ class Mac : public InstanceLocator, private NonCopyable uint16_t mCslPeriod; #endif uint8_t mWakeupChannel; - +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + uint32_t mWedListenInterval; + uint16_t mWedListenDuration; +#endif union { ActiveScanHandler mActiveScanHandler; diff --git a/src/core/mac/mac_links.hpp b/src/core/mac/mac_links.hpp index 4da89c0b79ca..7ca0d9ff8ee1 100644 --- a/src/core/mac/mac_links.hpp +++ b/src/core/mac/mac_links.hpp @@ -473,6 +473,25 @@ class Links : public InstanceLocator } #endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + /** + * Configures wake-up listening parameters in all radios. + * + * @param[in] aInterval The WED listen interval in microseconds. + * @param[in] aDuration The WED listen duration in microseconds. + * @param[in] aChannel The wake-up channel. + */ + void UpdateWakeupListening(uint32_t aInterval, uint16_t aDuration, uint8_t aChannel) + { + OT_UNUSED_VARIABLE(aInterval); + OT_UNUSED_VARIABLE(aDuration); + OT_UNUSED_VARIABLE(aChannel); +#if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE + mSubMac.UpdateWakeupListening(aInterval, aDuration, aChannel); +#endif + } +#endif + /** * Transitions all radio links to Receive. * diff --git a/src/core/mac/sub_mac.cpp b/src/core/mac/sub_mac.cpp index 9021a1dc0dfb..2188a60ebd27 100644 --- a/src/core/mac/sub_mac.cpp +++ b/src/core/mac/sub_mac.cpp @@ -53,6 +53,9 @@ SubMac::SubMac(Instance &aInstance) #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE , mCslTimer(aInstance, SubMac::HandleCslTimer) #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + , mWedTimer(aInstance, SubMac::HandleWedTimer) +#endif { #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE mCslParentAccuracy.Init(); @@ -90,6 +93,9 @@ void SubMac::Init(void) #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE CslInit(); #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + WedInit(); +#endif } otRadioCaps SubMac::GetCaps(void) const @@ -203,6 +209,9 @@ Error SubMac::Disable(void) #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE mCslTimer.Stop(); #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + mWedTimer.Stop(); +#endif mTimer.Stop(); SuccessOrExit(error = Get().Sleep()); diff --git a/src/core/mac/sub_mac.hpp b/src/core/mac/sub_mac.hpp index 3e7ff39635c0..586b4c1cb288 100644 --- a/src/core/mac/sub_mac.hpp +++ b/src/core/mac/sub_mac.hpp @@ -480,6 +480,17 @@ class SubMac : public InstanceLocator, private NonCopyable bool IsRadioFilterEnabled(void) const { return mRadioFilterEnabled; } #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + /** + * Configures wake-up listening parameters in all radios. + * + * @param[in] aInterval The WED listen interval in microseconds. + * @param[in] aDuration The WED listen duration in microseconds. + * @param[in] aChannel The wake-up channel. + */ + void UpdateWakeupListening(uint32_t aInterval, uint16_t aDuration, uint8_t aChannel); +#endif + private: #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE void CslInit(void); @@ -492,6 +503,11 @@ class SubMac : public InstanceLocator, private NonCopyable #if OPENTHREAD_CONFIG_MAC_CSL_DEBUG_ENABLE void LogReceived(RxFrame *aFrame); #endif +#endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + void WedInit(void); + static void HandleWedTimer(Timer &aTimer); + void HandleWedTimer(void); #endif static constexpr uint8_t kCsmaMinBe = 3; // macMinBE (IEEE 802.15.4-2006). @@ -530,16 +546,22 @@ class SubMac : public InstanceLocator, private NonCopyable #endif }; -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE // Radio on times needed before and after MHR time for proper frame detection static constexpr uint32_t kMinReceiveOnAhead = OPENTHREAD_CONFIG_MIN_RECEIVE_ON_AHEAD; static constexpr uint32_t kMinReceiveOnAfter = OPENTHREAD_CONFIG_MIN_RECEIVE_ON_AFTER; - // CSL receivers would wake up `kCslReceiveTimeAhead` earlier + // CSL/wake-up listening receivers would wake up `kCslReceiveTimeAhead` earlier // than expected sample window. The value is in usec. static constexpr uint32_t kCslReceiveTimeAhead = OPENTHREAD_CONFIG_CSL_RECEIVE_TIME_AHEAD; #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + // Margin to be applied after the end of a WED listen duration to schedule the next listen interval. + // The value is in usec. + static constexpr uint32_t kWedReceiveTimeAfter = OPENTHREAD_CONFIG_WED_RECEIVE_TIME_AFTER; +#endif + #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE // CSL transmitter would schedule delayed transmission `kCslTransmitTimeAhead` earlier // than expected delayed transmit time. The value is in usec. @@ -635,6 +657,15 @@ class SubMac : public InstanceLocator, private NonCopyable CslAccuracy mCslParentAccuracy; // The parent's CSL accuracy (clock accuracy and uncertainty). TimerMicro mCslTimer; #endif + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + uint32_t mWedListenInterval; // The WED listen interval, in microseconds. + uint16_t mWedListenDuration; // The WED listen duration, in microseconds. + uint8_t mWakeupChannel; // The wake-up sample channel. + TimeMicro mWedSampleTime; // The WED sample time of the current interval in local time. + uint64_t mWedSampleTimeRadio; // The WED sample time of the current interval in radio time. + TimerMicro mWedTimer; +#endif }; /** diff --git a/src/core/mac/sub_mac_wed.cpp b/src/core/mac/sub_mac_wed.cpp new file mode 100644 index 000000000000..3a6b2cf9fd8b --- /dev/null +++ b/src/core/mac/sub_mac_wed.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file implements the Wake-up End Device of the subset of IEEE 802.15.4 MAC primitives. + */ + +#include "sub_mac.hpp" + +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + +#include "instance/instance.hpp" + +namespace ot { +namespace Mac { + +RegisterLogModule("SubMac"); + +void SubMac::WedInit(void) +{ + mWedListenInterval = 0; + mWedTimer.Stop(); +} + +void SubMac::UpdateWakeupListening(uint32_t aInterval, uint16_t aDuration, uint8_t aChannel) +{ + VerifyOrExit(RadioSupportsReceiveTiming()); + VerifyOrExit(aInterval != mWedListenInterval || aDuration != mWedListenDuration || aChannel != mWakeupChannel); + + mWedListenInterval = aInterval; + mWedListenDuration = aDuration; + mWakeupChannel = aChannel; + mWedTimer.Stop(); + + if (mWedListenInterval > 0) + { + mWedSampleTime = TimerMicro::GetNow() + kCslReceiveTimeAhead - mWedListenInterval; + mWedSampleTimeRadio = otPlatRadioGetNow(&GetInstance()) + kCslReceiveTimeAhead - mWedListenInterval; + + HandleWedTimer(); + } + +exit: + return; +} + +void SubMac::HandleWedTimer(Timer &aTimer) { aTimer.Get().HandleWedTimer(); } + +void SubMac::HandleWedTimer(void) +{ + mWedSampleTime += mWedListenInterval; + mWedSampleTimeRadio += mWedListenInterval; + mWedTimer.FireAt(mWedSampleTime + mWedListenDuration + kWedReceiveTimeAfter); + + if (mState != kStateDisabled) + { + IgnoreError( + Get().ReceiveAt(mWakeupChannel, static_cast(mWedSampleTimeRadio), mWedListenDuration)); + } +} + +} // namespace Mac +} // namespace ot + +#endif // OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE diff --git a/src/core/radio/radio.hpp b/src/core/radio/radio.hpp index 4f7353fd83a6..d63f2bc1104d 100644 --- a/src/core/radio/radio.hpp +++ b/src/core/radio/radio.hpp @@ -51,6 +51,7 @@ namespace ot { static constexpr uint32_t kUsPerTenSymbols = OT_US_PER_TEN_SYMBOLS; ///< Time for 10 symbols in units of microseconds static constexpr uint32_t kRadioHeaderShrDuration = 160; ///< Duration of SHR in us static constexpr uint32_t kRadioHeaderPhrDuration = 32; ///< Duration of PHR in us +static constexpr uint32_t kOctetDuration = 32; ///< Duration of one octet in us static constexpr int8_t kRadioPowerInvalid = OT_RADIO_POWER_INVALID; ///< Invalid TX power value @@ -62,6 +63,13 @@ static constexpr uint64_t kMinCslPeriod = OPENTHREAD_CONFIG_MAC_CSL_MIN_PERIOD static constexpr uint64_t kMaxCslTimeout = OPENTHREAD_CONFIG_MAC_CSL_MAX_TIMEOUT; #endif +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +/** + * Minimum WED listen duration supported in us. + */ +static constexpr uint32_t kMinWedListenDuration = 100; +#endif + /** * @addtogroup core-radio * @@ -473,14 +481,7 @@ class Radio : public InstanceLocator, private NonCopyable */ Error Receive(uint8_t aChannel); -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE - /** - * Updates the CSL sample time in radio. - * - * @param[in] aCslSampleTime The CSL sample time. - */ - void UpdateCslSampleTime(uint32_t aCslSampleTime); - +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE /** * Schedules a radio reception window at a specific time and duration. * @@ -492,6 +493,15 @@ class Radio : public InstanceLocator, private NonCopyable * @retval kErrorFailed The receive window could not be scheduled. */ Error ReceiveAt(uint8_t aChannel, uint32_t aStart, uint32_t aDuration); +#endif + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE + /** + * Updates the CSL sample time in radio. + * + * @param[in] aCslSampleTime The CSL sample time. + */ + void UpdateCslSampleTime(uint32_t aCslSampleTime); /** * Enables CSL sampling in radio. @@ -894,12 +904,7 @@ inline Error Radio::Receive(uint8_t aChannel) return otPlatRadioReceive(GetInstancePtr(), aChannel); } -#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE -inline void Radio::UpdateCslSampleTime(uint32_t aCslSampleTime) -{ - otPlatRadioUpdateCslSampleTime(GetInstancePtr(), aCslSampleTime); -} - +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE inline Error Radio::ReceiveAt(uint8_t aChannel, uint32_t aStart, uint32_t aDuration) { Error error = otPlatRadioReceiveAt(GetInstancePtr(), aChannel, aStart, aDuration); @@ -911,6 +916,13 @@ inline Error Radio::ReceiveAt(uint8_t aChannel, uint32_t aStart, uint32_t aDurat #endif return error; } +#endif + +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE +inline void Radio::UpdateCslSampleTime(uint32_t aCslSampleTime) +{ + otPlatRadioUpdateCslSampleTime(GetInstancePtr(), aCslSampleTime); +} inline Error Radio::EnableCsl(uint32_t aCslPeriod, otShortAddress aShortAddr, const otExtAddress *aExtAddr) { @@ -1015,11 +1027,13 @@ inline Error Radio::Sleep(void) { return kErrorNone; } inline Error Radio::Receive(uint8_t) { return kErrorNone; } +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +inline Error Radio::ReceiveAt(uint8_t, uint32_t, uint32_t) { return kErrorNone; } +#endif + #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE inline void Radio::UpdateCslSampleTime(uint32_t) {} -inline Error Radio::ReceiveAt(uint8_t, uint32_t, uint32_t) { return kErrorNone; } - inline Error Radio::EnableCsl(uint32_t, otShortAddress aShortAddr, const otExtAddress *) { return kErrorNotImplemented; diff --git a/src/core/thread/key_manager.cpp b/src/core/thread/key_manager.cpp index 8c83bd91be94..8b8eb9073af6 100644 --- a/src/core/thread/key_manager.cpp +++ b/src/core/thread/key_manager.cpp @@ -400,6 +400,18 @@ const Mle::KeyMaterial &KeyManager::GetTemporaryMleKey(uint32_t aKeySequence) return mTemporaryMleKey; } +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE +const Mle::KeyMaterial &KeyManager::GetTemporaryMacKey(uint32_t aKeySequence) +{ + HashKeys hashKeys; + + ComputeKeys(aKeySequence, hashKeys); + mTemporaryMacKey.SetFrom(hashKeys.GetMacKey()); + + return mTemporaryMacKey; +} +#endif + #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE const Mac::KeyMaterial &KeyManager::GetTemporaryTrelMacKey(uint32_t aKeySequence) { diff --git a/src/core/thread/key_manager.hpp b/src/core/thread/key_manager.hpp index 85dbb24fe5d2..2aafa3658245 100644 --- a/src/core/thread/key_manager.hpp +++ b/src/core/thread/key_manager.hpp @@ -358,6 +358,15 @@ class KeyManager : public InstanceLocator, private NonCopyable */ const Mle::KeyMaterial &GetTemporaryMleKey(uint32_t aKeySequence); + /** + * Returns a temporary MAC key Material computed from the given key sequence. + * + * @param[in] aKeySequence The key sequence value. + * + * @returns The temporary MAC key. + */ + const Mle::KeyMaterial &GetTemporaryMacKey(uint32_t aKeySequence); + #if OPENTHREAD_CONFIG_RADIO_LINK_IEEE_802_15_4_ENABLE /** * Returns the current MAC Frame Counter value for 15.4 radio link. @@ -598,6 +607,10 @@ class KeyManager : public InstanceLocator, private NonCopyable Mle::KeyMaterial mMleKey; Mle::KeyMaterial mTemporaryMleKey; +#if OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE + Mle::KeyMaterial mTemporaryMacKey; +#endif + #if OPENTHREAD_CONFIG_RADIO_LINK_TREL_ENABLE Mac::KeyMaterial mTrelKey; Mac::KeyMaterial mTemporaryTrelKey; diff --git a/src/core/thread/tmf.hpp b/src/core/thread/tmf.hpp index d95afb4f638e..d33a3e3828e7 100644 --- a/src/core/thread/tmf.hpp +++ b/src/core/thread/tmf.hpp @@ -98,7 +98,6 @@ class MessageInfo : public InstanceLocator, public Ip6::MessageInfo /** * Sets the local socket address to RLOC address and the peer socket address to leader RLOC. -q * */ void SetSockAddrToRlocPeerAddrToLeaderRloc(void);