From e653478c503d5b13207b01938fa1fa494a8b87d3 Mon Sep 17 00:00:00 2001 From: Jintao Lin Date: Wed, 17 Jun 2020 13:53:29 +0800 Subject: [PATCH] [posix] get time offset between host and rcp (#5089) To support CSL on RCP mode, POSIX host needs to know current time on RCP. This commit adds function to periodically calculate the time offset between host and RCP so that host can get an estimated RCP time by adding its current time with this offset. --- examples/platforms/nrf528xx/src/alarm.c | 2 - include/openthread/instance.h | 2 +- include/openthread/link_raw.h | 10 +++ include/openthread/platform/radio.h | 10 +++ src/core/api/link_raw_api.cpp | 7 ++ src/core/radio/radio_platform.cpp | 13 ++++ src/lib/spinel/radio_spinel.hpp | 41 +++++++++++ src/lib/spinel/radio_spinel_impl.hpp | 95 +++++++++++++++++++++++++ src/lib/spinel/spinel.h | 10 +++ src/ncp/ncp_base_dispatcher.cpp | 5 ++ src/ncp/ncp_base_radio.cpp | 11 +++ src/posix/platform/radio.cpp | 43 +++++++---- 12 files changed, 231 insertions(+), 18 deletions(-) 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(); +}