diff --git a/examples/platforms/nrf528xx/src/alarm.c b/examples/platforms/nrf528xx/src/alarm.c index 3d890174b..5e74eaaca 100644 --- a/examples/platforms/nrf528xx/src/alarm.c +++ b/examples/platforms/nrf528xx/src/alarm.c @@ -688,7 +688,6 @@ void RTC_IRQ_HANDLER(void) } } -#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE uint64_t otPlatTimeGet(void) { return nrf5AlarmGetCurrentTime(); @@ -698,4 +697,3 @@ uint16_t otPlatTimeGetXtalAccuracy(void) { return XTAL_ACCURACY; } -#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE diff --git a/include/openthread/instance.h b/include/openthread/instance.h index ebfa46028..bbb994d6d 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (5) +#define OPENTHREAD_API_VERSION (6) /** * @addtogroup api-instance diff --git a/include/openthread/link_raw.h b/include/openthread/link_raw.h index 5d5c90825..f6d5199db 100644 --- a/include/openthread/link_raw.h +++ b/include/openthread/link_raw.h @@ -363,6 +363,16 @@ otError otLinkRawSetMacKey(otInstance * aInstance, */ otError otLinkRawSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCounter); +/** + * Get current platform time (64bits width) of the radio chip. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The current radio time in microseconds. + * + */ +uint64_t otLinkRawGetRadioTime(otInstance *aInstance); + /** * @} * diff --git a/include/openthread/platform/radio.h b/include/openthread/platform/radio.h index 8701115cd..23a5a5df4 100644 --- a/include/openthread/platform/radio.h +++ b/include/openthread/platform/radio.h @@ -489,6 +489,16 @@ void otPlatRadioSetMacKey(otInstance * aInstance, */ void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCounter); +/** + * Get the current estimated time (64bits width) of the radio chip. + * + * @param[in] aInstance A pointer to an OpenThread instance. + * + * @returns The current time in microseconds. UINT64_MAX when platform does not support or radio time is not ready. + * + */ +uint64_t otPlatRadioGetNow(otInstance *aInstance); + /** * @} * diff --git a/src/core/api/link_raw_api.cpp b/src/core/api/link_raw_api.cpp index f887eabf4..e753606cd 100644 --- a/src/core/api/link_raw_api.cpp +++ b/src/core/api/link_raw_api.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include "common/debug.hpp" #include "common/instance.hpp" @@ -238,6 +239,12 @@ otError otLinkRawSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCou return static_cast(aInstance)->Get().SetMacFrameCounter(aMacFrameCounter); } +uint64_t otLinkRawGetRadioTime(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return otPlatTimeGet(); +} + #if OPENTHREAD_RADIO otDeviceRole otThreadGetDeviceRole(otInstance *aInstance) diff --git a/src/core/radio/radio_platform.cpp b/src/core/radio/radio_platform.cpp index 6839f148f..06d8b1b0d 100644 --- a/src/core/radio/radio_platform.cpp +++ b/src/core/radio/radio_platform.cpp @@ -31,6 +31,7 @@ */ #include +#include #include "common/instance.hpp" #include "radio/radio.hpp" @@ -145,3 +146,15 @@ OT_TOOL_WEAK void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t OT_UNUSED_VARIABLE(aInstance); OT_UNUSED_VARIABLE(aMacFrameCounter); } + +OT_TOOL_WEAK uint64_t otPlatTimeGet(void) +{ + return UINT64_MAX; +} + +OT_TOOL_WEAK uint64_t otPlatRadioGetNow(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + return UINT64_MAX; +} diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp index dfed95b67..600f7c1cc 100644 --- a/src/lib/spinel/radio_spinel.hpp +++ b/src/lib/spinel/radio_spinel.hpp @@ -653,6 +653,22 @@ public: */ otError RestoreDatasetFromNcp(void); + /** + * This method returns the next timepoint to recalculate RCP time offset. + * + * @returns The timepoint to start the recalculation of RCP time offset. + * + */ + uint64_t GetNextRadioTimeRecalcStart(void) const { return mRadioTimeRecalcStart; } + + /** + * This method gets the current estimated time on RCP. + * + * @returns The current estimated RCP time in microseconds. + * + */ + uint64_t GetNow(void); + private: enum { @@ -705,6 +721,26 @@ private: */ otError Get(spinel_prop_key_t aKey, const char *aFormat, ...); + /** + * This method tries to retrieve a spinel property from OpenThread transceiver with parameter appended. + * + * @param[in] aKey Spinel property key. + * @param[in] aParam Parameter appended to spinel command. + * @param[in] aParamSize Size of parameter appended to spinel command + * @param[in] aFormat Spinel formatter to unpack property value. + * @param[out] ... Variable arguments list. + * + * @retval OT_ERROR_NONE Successfully got the property. + * @retval OT_ERROR_BUSY Failed due to another operation is on going. + * @retval OT_ERROR_RESPONSE_TIMEOUT Failed due to no response received from the transceiver. + * + */ + otError GetWithParam(spinel_prop_key_t aKey, + const uint8_t * aParam, + spinel_size_t aParamSize, + const char * aFormat, + ...); + /** * This method tries to update a spinel property of OpenThread transceiver. * @@ -790,6 +826,8 @@ private: void TransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError); + void CalcRcpTimeOffset(void); + otInstance *mInstance; SpinelInterface::RxFrameBuffer mRxFrameBuffer; @@ -828,6 +866,7 @@ private: bool mIsPromiscuous : 1; ///< Promiscuous mode. bool mIsReady : 1; ///< NCP ready. bool mSupportsLogStream : 1; ///< RCP supports `LOG_STREAM` property with OpenThread log meta-data format. + bool mIsTimeSynced : 1; ///< Host has calculated the time difference between host and RCP. #if OPENTHREAD_CONFIG_DIAG_ENABLE bool mDiagMode; @@ -836,6 +875,8 @@ private: #endif uint64_t mTxRadioEndUs; + uint64_t mRadioTimeRecalcStart; ///< When to recalculate RCP time offset. + int64_t mRadioTimeOffset; ///< Time difference with estimated RCP time minus host time. }; } // namespace Spinel diff --git a/src/lib/spinel/radio_spinel_impl.hpp b/src/lib/spinel/radio_spinel_impl.hpp index a4b0e75b6..9fb118bd4 100644 --- a/src/lib/spinel/radio_spinel_impl.hpp +++ b/src/lib/spinel/radio_spinel_impl.hpp @@ -68,6 +68,10 @@ #define TX_WAIT_US (5 * US_PER_S) #endif +#ifndef RCP_TIME_OFFSET_CHECK_INTERVAL +#define RCP_TIME_OFFSET_CHECK_INTERVAL (60 * US_PER_S) +#endif + using ot::Spinel::Decoder; namespace ot { @@ -187,12 +191,15 @@ RadioSpinel::RadioSpinel(void) , mIsPromiscuous(false) , mIsReady(false) , mSupportsLogStream(false) + , mIsTimeSynced(false) #if OPENTHREAD_CONFIG_DIAG_ENABLE , mDiagMode(false) , mDiagOutput(NULL) , mDiagOutputMaxLen(0) #endif , mTxRadioEndUs(UINT64_MAX) + , mRadioTimeRecalcStart(UINT64_MAX) + , mRadioTimeOffset(0) { mVersion[0] = '\0'; } @@ -987,6 +994,7 @@ void RadioSpinel::Process(const ProcessContex } ProcessRadioStateMachine(); + CalcRcpTimeOffset(); } template @@ -1253,6 +1261,27 @@ otError RadioSpinel::Get(spinel_prop_key_t aK return error; } +// This is not a normal use case for VALUE_GET command and should be only used to get RCP timestamp with dummy payload +template +otError RadioSpinel::GetWithParam(spinel_prop_key_t aKey, + const uint8_t * aParam, + spinel_size_t aParamSize, + const char * aFormat, + ...) +{ + otError error; + + assert(mWaitingTid == 0); + + mPropertyFormat = aFormat; + va_start(mPropertyArgs, aFormat); + error = Request(true, SPINEL_CMD_PROP_VALUE_GET, aKey, SPINEL_DATATYPE_DATA_S, aParam, aParamSize); + va_end(mPropertyArgs); + mPropertyFormat = NULL; + + return error; +} + template otError RadioSpinel::Set(spinel_prop_key_t aKey, const char *aFormat, ...) { @@ -1696,5 +1725,71 @@ otRadioState RadioSpinel::GetState(void) cons return sOtRadioStateMap[mState]; } +template +void RadioSpinel::CalcRcpTimeOffset(void) +{ +#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE + otError error = OT_ERROR_NONE; + uint64_t localTxTimestamp; + uint64_t localRxTimestamp; + uint64_t remoteTimestamp = 0; + uint8_t buffer[sizeof(remoteTimestamp)]; + spinel_ssize_t packed; + + otLogInfoPlat("Trying to get RCP time offset"); + + /* + * Use a modified Network Time Protocol(NTP) to calculate the time offset + * Assume the time offset is D so that local can calculate remote time with, + * T' = T + D + * Where T is the local time and T' is the remote time. + * The time offset is calculated using timestamp measured at local and remote. + * + * T0 P P T2 + * local time --+----+----+---> + * \ | ^ + * get\ | /is + * v | / + * remote time -------+---------> + * T1' + * + * Based on the assumptions, + * 1. If the propagation time(P) from local to remote and from remote to local are same. + * 2. Both the host and RCP can accurately measure the time they send or receive a message. + * The degree to which these assumptions hold true determines the accuracy of the offset. + * Then, + * T1' = T0 + P + D and T1' = T2 - P + D + * Time offset can be calculated with, + * D = T1' - ((T0 + T2)/ 2) + */ + + VerifyOrExit(!mIsTimeSynced || (otPlatTimeGet() >= GetNextRadioTimeRecalcStart()), OT_NOOP); + packed = spinel_datatype_pack(buffer, sizeof(buffer), SPINEL_DATATYPE_UINT64_S, remoteTimestamp); + VerifyOrExit(packed > 0 && static_cast(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); + + localTxTimestamp = otPlatTimeGet(); + + // Dummy timestamp payload to make request length same as response + error = GetWithParam(SPINEL_PROP_RCP_TIMESTAMP, buffer, packed, SPINEL_DATATYPE_UINT64_S, &remoteTimestamp); + + localRxTimestamp = otPlatTimeGet(); + + VerifyOrExit(error == OT_ERROR_NONE, mRadioTimeRecalcStart = localRxTimestamp); + + mRadioTimeOffset = remoteTimestamp - ((localRxTimestamp / 2) + (localTxTimestamp / 2)); + mIsTimeSynced = true; + mRadioTimeRecalcStart = localRxTimestamp + RCP_TIME_OFFSET_CHECK_INTERVAL; + +exit: + LogIfFail("Error calculating RCP time offset: %s", error); +#endif // OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE +} + +template +uint64_t RadioSpinel::GetNow(void) +{ + return mIsTimeSynced ? (otPlatTimeGet() + static_cast(mRadioTimeOffset)) : UINT64_MAX; +} + } // namespace Spinel } // namespace ot diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h index 9db58800b..dcc6e06ac 100644 --- a/src/lib/spinel/spinel.h +++ b/src/lib/spinel/spinel.h @@ -3963,6 +3963,16 @@ enum */ SPINEL_PROP_RCP_MAC_FRAME_COUNTER = SPINEL_PROP_RCP__BEGIN + 1, + /// Timestamps when Spinel frame is received and transmitted + /** Format: `X`. + * + * `X`: Spinel frame transmit timestamp + * + * The Spinel property is used to get timestamp from RCP to calculate host and RCP timer difference. + * + */ + SPINEL_PROP_RCP_TIMESTAMP = SPINEL_PROP_RCP__BEGIN + 2, + SPINEL_PROP_RCP__END = 0x900, SPINEL_PROP_NEST__BEGIN = 0x3BC0, diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp index 8987134db..bf6eb5e32 100644 --- a/src/ncp/ncp_base_dispatcher.cpp +++ b/src/ncp/ncp_base_dispatcher.cpp @@ -198,6 +198,11 @@ NcpBase::PropertyHandler NcpBase::FindGetPropertyHandler(spinel_prop_key_t aKey) #if OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_CNTR_MAC_RETRY_HISTOGRAM), #endif +#endif // OPENTHREAD_MTD || OPENTHREAD_FTD +#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE + OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_RCP_TIMESTAMP), +#endif +#if OPENTHREAD_MTD || OPENTHREAD_FTD OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_UNSOL_UPDATE_FILTER), OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_UNSOL_UPDATE_LIST), #if OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE diff --git a/src/ncp/ncp_base_radio.cpp b/src/ncp/ncp_base_radio.cpp index 6fdbeca64..6863ffa8e 100644 --- a/src/ncp/ncp_base_radio.cpp +++ b/src/ncp/ncp_base_radio.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include "common/code_utils.hpp" #include "common/debug.hpp" @@ -221,6 +222,16 @@ template <> otError NcpBase::HandlePropertyGet otError NcpBase::HandlePropertyGet(void) +{ + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = mEncoder.WriteUint64(otLinkRawGetRadioTime(mInstance))); + +exit: + return error; +} + template <> otError NcpBase::HandlePropertySet(void) { otError error = OT_ERROR_NONE; diff --git a/src/posix/platform/radio.cpp b/src/posix/platform/radio.cpp index 9174a892e..50786af5e 100644 --- a/src/posix/platform/radio.cpp +++ b/src/posix/platform/radio.cpp @@ -214,30 +214,37 @@ bool otPlatRadioGetPromiscuous(otInstance *aInstance) void platformRadioUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, int *aMaxFd, struct timeval *aTimeout) { - sRadioSpinel.GetSpinelInterface().UpdateFdSet(*aReadFdSet, *aWriteFdSet, *aMaxFd, *aTimeout); + uint64_t now = otPlatTimeGet(); + uint64_t deadline = sRadioSpinel.GetNextRadioTimeRecalcStart(); if (sRadioSpinel.IsTransmitting()) { - uint64_t now = otPlatTimeGet(); uint64_t txRadioEndUs = sRadioSpinel.GetTxRadioEndUs(); - if (now < txRadioEndUs) + if (txRadioEndUs < deadline) { - uint64_t remain = txRadioEndUs - now; - - if (remain < static_cast(aTimeout->tv_sec * US_PER_S + aTimeout->tv_usec)) - { - aTimeout->tv_sec = static_cast(remain / US_PER_S); - aTimeout->tv_usec = static_cast(remain % US_PER_S); - } - } - else - { - aTimeout->tv_sec = 0; - aTimeout->tv_usec = 0; + deadline = txRadioEndUs; } } + if (now < deadline) + { + uint64_t remain = deadline - now; + + if (remain < static_cast(aTimeout->tv_sec * US_PER_S + aTimeout->tv_usec)) + { + aTimeout->tv_sec = static_cast(remain / US_PER_S); + aTimeout->tv_usec = static_cast(remain % US_PER_S); + } + } + else + { + aTimeout->tv_sec = 0; + aTimeout->tv_usec = 0; + } + + sRadioSpinel.GetSpinelInterface().UpdateFdSet(*aReadFdSet, *aWriteFdSet, *aMaxFd, *aTimeout); + if (sRadioSpinel.HasPendingFrame() || sRadioSpinel.IsTransmitDone()) { aTimeout->tv_sec = 0; @@ -495,3 +502,9 @@ void otPlatRadioSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCoun SuccessOrDie(sRadioSpinel.SetMacFrameCounter(aMacFrameCounter)); OT_UNUSED_VARIABLE(aInstance); } + +uint64_t otPlatRadioGetNow(otInstance *aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + return sRadioSpinel.GetNow(); +}