mirror of
https://github.com/espressif/openthread.git
synced 2026-08-07 03:07:47 +00:00
[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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
@@ -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);
|
||||
|
||||
/**
|
||||
* @}
|
||||
*
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
#include <openthread/diag.h>
|
||||
#include <openthread/thread.h>
|
||||
#include <openthread/platform/diag.h>
|
||||
#include <openthread/platform/time.h>
|
||||
|
||||
#include "common/debug.hpp"
|
||||
#include "common/instance.hpp"
|
||||
@@ -238,6 +239,12 @@ otError otLinkRawSetMacFrameCounter(otInstance *aInstance, uint32_t aMacFrameCou
|
||||
return static_cast<Instance *>(aInstance)->Get<Mac::LinkRaw>().SetMacFrameCounter(aMacFrameCounter);
|
||||
}
|
||||
|
||||
uint64_t otLinkRawGetRadioTime(otInstance *aInstance)
|
||||
{
|
||||
OT_UNUSED_VARIABLE(aInstance);
|
||||
return otPlatTimeGet();
|
||||
}
|
||||
|
||||
#if OPENTHREAD_RADIO
|
||||
|
||||
otDeviceRole otThreadGetDeviceRole(otInstance *aInstance)
|
||||
|
||||
@@ -31,6 +31,7 @@
|
||||
*/
|
||||
|
||||
#include <openthread/instance.h>
|
||||
#include <openthread/platform/time.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<InterfaceType, ProcessContextType>::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<InterfaceType, ProcessContextType>::Process(const ProcessContex
|
||||
}
|
||||
|
||||
ProcessRadioStateMachine();
|
||||
CalcRcpTimeOffset();
|
||||
}
|
||||
|
||||
template <typename InterfaceType, typename ProcessContextType>
|
||||
@@ -1253,6 +1261,27 @@ otError RadioSpinel<InterfaceType, ProcessContextType>::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 <typename InterfaceType, typename ProcessContextType>
|
||||
otError RadioSpinel<InterfaceType, ProcessContextType>::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 <typename InterfaceType, typename ProcessContextType>
|
||||
otError RadioSpinel<InterfaceType, ProcessContextType>::Set(spinel_prop_key_t aKey, const char *aFormat, ...)
|
||||
{
|
||||
@@ -1696,5 +1725,71 @@ otRadioState RadioSpinel<InterfaceType, ProcessContextType>::GetState(void) cons
|
||||
return sOtRadioStateMap[mState];
|
||||
}
|
||||
|
||||
template <typename InterfaceType, typename ProcessContextType>
|
||||
void RadioSpinel<InterfaceType, ProcessContextType>::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<size_t>(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 <typename InterfaceType, typename ProcessContextType>
|
||||
uint64_t RadioSpinel<InterfaceType, ProcessContextType>::GetNow(void)
|
||||
{
|
||||
return mIsTimeSynced ? (otPlatTimeGet() + static_cast<uint64_t>(mRadioTimeOffset)) : UINT64_MAX;
|
||||
}
|
||||
|
||||
} // namespace Spinel
|
||||
} // namespace ot
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
#include <openthread/link_raw.h>
|
||||
#include <openthread/ncp.h>
|
||||
#include <openthread/platform/radio.h>
|
||||
#include <openthread/platform/time.h>
|
||||
|
||||
#include "common/code_utils.hpp"
|
||||
#include "common/debug.hpp"
|
||||
@@ -221,6 +222,16 @@ template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SRC_MATCH_ENABLED
|
||||
return mEncoder.WriteBool(mSrcMatchEnabled);
|
||||
}
|
||||
|
||||
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RCP_TIMESTAMP>(void)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
SuccessOrExit(error = mEncoder.WriteUint64(otLinkRawGetRadioTime(mInstance)));
|
||||
|
||||
exit:
|
||||
return error;
|
||||
}
|
||||
|
||||
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SRC_MATCH_ENABLED>(void)
|
||||
{
|
||||
otError error = OT_ERROR_NONE;
|
||||
|
||||
@@ -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<uint64_t>(aTimeout->tv_sec * US_PER_S + aTimeout->tv_usec))
|
||||
{
|
||||
aTimeout->tv_sec = static_cast<time_t>(remain / US_PER_S);
|
||||
aTimeout->tv_usec = static_cast<suseconds_t>(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<uint64_t>(aTimeout->tv_sec * US_PER_S + aTimeout->tv_usec))
|
||||
{
|
||||
aTimeout->tv_sec = static_cast<time_t>(remain / US_PER_S);
|
||||
aTimeout->tv_usec = static_cast<suseconds_t>(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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user