From 9277bce08e233c59532137a135d963c05f6caa0b Mon Sep 17 00:00:00 2001 From: Yakun Xu Date: Tue, 19 Nov 2024 00:53:55 +0800 Subject: [PATCH] [spinel] add SPINEL_PROP_MAC_RX_AT (#10921) This commit adds receive at functionality for spinel for host to schedule reception on RCP. --- src/lib/spinel/radio_spinel.cpp | 14 +++++++ src/lib/spinel/radio_spinel.hpp | 16 ++++++++ src/lib/spinel/spinel.h | 11 ++++++ src/ncp/ncp_base.cpp | 26 +++++++++++++ src/ncp/ncp_base_dispatcher.cpp | 1 + tests/gtest/fake_platform.cpp | 55 ++++++++++++++++++++------- tests/gtest/fake_platform.hpp | 35 +++++++++++++++-- tests/gtest/radio_spinel_rcp_test.cpp | 28 ++++++++++++++ 8 files changed, 169 insertions(+), 17 deletions(-) diff --git a/src/lib/spinel/radio_spinel.cpp b/src/lib/spinel/radio_spinel.cpp index 82a829226..185495c65 100644 --- a/src/lib/spinel/radio_spinel.cpp +++ b/src/lib/spinel/radio_spinel.cpp @@ -1727,6 +1727,20 @@ exit: return error; } +otError RadioSpinel::ReceiveAt(uint64_t aWhen, uint32_t aDuration, uint8_t aChannel) +{ + otError error = OT_ERROR_NONE; + + EXPECT(mState != kStateDisabled, error = OT_ERROR_INVALID_STATE); + + error = Set(SPINEL_PROP_MAC_RX_AT, SPINEL_DATATYPE_UINT64_S SPINEL_DATATYPE_UINT32_S SPINEL_DATATYPE_UINT8_S, aWhen, + aDuration, aChannel); + EXPECT_NO_ERROR(error); + +exit: + return error; +} + otError RadioSpinel::Sleep(void) { otError error = OT_ERROR_NONE; diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp index a83db5f03..f773ead5b 100644 --- a/src/lib/spinel/radio_spinel.hpp +++ b/src/lib/spinel/radio_spinel.hpp @@ -569,6 +569,22 @@ public: */ otError Receive(uint8_t aChannel); + /** + * Schedule a radio reception window at a specific time and duration. + * + * @param[in] aWhen The receive window start time in the local + * radio clock, see `otPlatRadioGetNow`. The radio + * receiver SHALL be on and ready to receive the first + * symbol of a frame's SHR at the window start time. + * @param[in] aDuration The receive window duration, in microseconds, as + * measured by the local radio clock. + * @param[in] aChannel The channel to use for receiving. + * + * @retval OT_ERROR_NONE Successfully scheduled the reception. + * @retval OT_ERROR_INVALID_STATE The radio was disabled. + */ + otError ReceiveAt(uint64_t aWhen, uint32_t aDuration, uint8_t aChannel); + /** * Switches the radio state from Receive to Sleep. * diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h index 60b1945a5..218f281f9 100644 --- a/src/lib/spinel/spinel.h +++ b/src/lib/spinel/spinel.h @@ -2101,6 +2101,17 @@ enum */ SPINEL_PROP_MAC_15_4_ALT_SADDR = SPINEL_PROP_MAC__BEGIN + 12, + /// MAC Receive At + /** Format: `XLC` + * + * Schedule a radio reception window at a specific time and duration. + * + * `X`: The receive window start time. + * `L`: The receive window duration. + * `C`: The receive channel. + */ + SPINEL_PROP_MAC_RX_AT = SPINEL_PROP_MAC__BEGIN + 13, + SPINEL_PROP_MAC__END = 0x40, SPINEL_PROP_MAC_EXT__BEGIN = 0x1300, diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index 20b3d015a..b8646c019 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -46,6 +46,7 @@ #include "common/code_utils.hpp" #include "common/debug.hpp" +#include "lib/spinel/spinel.h" #include "radio/radio.hpp" namespace ot { @@ -1685,6 +1686,31 @@ exit: return error; } +template <> otError NcpBase::HandlePropertySet(void) +{ + otError error = OT_ERROR_NONE; + uint64_t when; + uint32_t duration; + uint8_t channel; + + SuccessOrExit(error = mDecoder.ReadUint64(when)); + SuccessOrExit(error = mDecoder.ReadUint32(duration)); + SuccessOrExit(error = mDecoder.ReadUint8(channel)); + + { + uint64_t now = otPlatRadioGetNow(mInstance); + uint32_t start; + + VerifyOrExit(when > now && (when - now) < UINT32_MAX, error = OT_ERROR_INVALID_ARGS); + + start = when - now; + error = otPlatRadioReceiveAt(mInstance, channel, start, duration); + } + +exit: + return error; +} + #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE template <> otError NcpBase::HandlePropertyGet(void) { diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp index 60db0e65f..ae6530596 100644 --- a/src/ncp/ncp_base_dispatcher.cpp +++ b/src/ncp/ncp_base_dispatcher.cpp @@ -451,6 +451,7 @@ NcpBase::PropertyHandler NcpBase::FindSetPropertyHandler(spinel_prop_key_t aKey) #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_15_4_ALT_SADDR), #endif + OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MAC_RX_AT), #if OPENTHREAD_MTD || OPENTHREAD_FTD OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_NET_IF_UP), OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_NET_STACK_UP), diff --git a/tests/gtest/fake_platform.cpp b/tests/gtest/fake_platform.cpp index db915b10f..5f109e075 100644 --- a/tests/gtest/fake_platform.cpp +++ b/tests/gtest/fake_platform.cpp @@ -39,8 +39,6 @@ #include #include #include -#include -#include #include #include #include @@ -124,11 +122,15 @@ void FakePlatform::StartMilliAlarm(uint32_t aT0, uint32_t aDt) void FakePlatform::StopMilliAlarm() { mMilliAlarmStart = kAlarmStop; } -void FakePlatform::ProcessAlarm(uint64_t &aTimeout) -{ - uint64_t end = mNow + aTimeout; +template <> void FakePlatform::HandleSchedule<&FakePlatform::mReceiveAtStart>() { mChannel = mReceiveAtChannel; } - uint64_t *alarm = &end; +template <> void FakePlatform::HandleSchedule<&FakePlatform::mReceiveAtEnd>() { mChannel = 0; } + +void FakePlatform::ProcessSchedules(uint64_t &aTimeout) +{ + uint64_t guard = mNow + aTimeout; + + uint64_t *alarm = &guard; #if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE if (mMicroAlarmStart < *alarm) { @@ -139,6 +141,14 @@ void FakePlatform::ProcessAlarm(uint64_t &aTimeout) { alarm = &mMilliAlarmStart; } + if (mReceiveAtStart < *alarm) + { + alarm = &mReceiveAtStart; + } + else if (mReceiveAtEnd < *alarm) + { + alarm = &mReceiveAtEnd; + } if (mNow < *alarm) { @@ -146,17 +156,29 @@ void FakePlatform::ProcessAlarm(uint64_t &aTimeout) mNow = *alarm; } *alarm = kAlarmStop; + + if (alarm == &guard) + { + // nonthing scheduled within this period. + } + else if (alarm == &mReceiveAtEnd) + { + FakePlatform::HandleSchedule<&FakePlatform::mReceiveAtEnd>(); + } + else if (alarm == &mReceiveAtStart) + { + FakePlatform::HandleSchedule<&FakePlatform::mReceiveAtStart>(); + } + else if (alarm == &mMilliAlarmStart) + { + FakePlatform::HandleSchedule<&FakePlatform::mMilliAlarmStart>(); + } #if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE - if (alarm == &mMicroAlarmStart) + else if (alarm == &mMicroAlarmStart) { - otPlatAlarmMicroFired(mInstance); + FakePlatform::HandleSchedule<&FakePlatform::mMicroAlarmStart>(); } - else #endif - if (alarm == &mMilliAlarmStart) - { - otPlatAlarmMilliFired(mInstance); - } } uint64_t FakePlatform::Run(uint64_t aTimeoutInUs) @@ -167,7 +189,7 @@ uint64_t FakePlatform::Run(uint64_t aTimeoutInUs) } else { - ProcessAlarm(aTimeoutInUs); + ProcessSchedules(aTimeoutInUs); } return aTimeoutInUs; @@ -356,6 +378,11 @@ otError otPlatRadioSleep(otInstance *) { return OT_ERROR_NONE; } otError otPlatRadioReceive(otInstance *, uint8_t aChannel) { return FakePlatform::CurrentPlatform().Receive(aChannel); } +otError otPlatRadioReceiveAt(otInstance *, uint8_t aChannel, uint32_t aStart, uint32_t aDuration) +{ + return FakePlatform::CurrentPlatform().ReceiveAt(aChannel, aStart, aDuration); +} + otError otPlatRadioTransmit(otInstance *, otRadioFrame *aFrame) { return FakePlatform::CurrentPlatform().Transmit(aFrame); diff --git a/tests/gtest/fake_platform.hpp b/tests/gtest/fake_platform.hpp index b10c7ca1b..1cabe3902 100644 --- a/tests/gtest/fake_platform.hpp +++ b/tests/gtest/fake_platform.hpp @@ -38,6 +38,8 @@ #include #include +#include +#include #include #include @@ -77,7 +79,16 @@ public: uint8_t GetReceiveChannel(void) const { return mChannel; } virtual otRadioFrame *GetTransmitBuffer() { return &mTransmitFrame; } virtual otError Transmit(otRadioFrame *aFrame); - virtual otError Receive(uint8_t aChannel) + virtual otError ReceiveAt(uint8_t aChannel, uint32_t aStart, uint32_t aDuration) + { + mReceiveAtChannel = aChannel; + mReceiveAtStart = mNow + aStart; + mReceiveAtEnd = mReceiveAtStart + aDuration; + + return OT_ERROR_NONE; + } + + virtual otError Receive(uint8_t aChannel) { mChannel = aChannel; return OT_ERROR_NONE; @@ -98,7 +109,7 @@ public: virtual uint64_t GetEui64() const { return 0; } protected: - void ProcessAlarm(uint64_t &aTimeout); + void ProcessSchedules(uint64_t &aTimeout); static constexpr uint64_t kAlarmStop = 0xffffffffffffffffUL; @@ -115,15 +126,33 @@ protected: #endif uint64_t mMilliAlarmStart = kAlarmStop; + uint64_t mReceiveAtStart = kAlarmStop; + uint64_t mReceiveAtEnd = kAlarmStop; + + template void HandleSchedule(); + otRadioFrame mTransmitFrame; uint8_t mTransmitBuffer[OT_RADIO_FRAME_MAX_SIZE]; - uint8_t mChannel = 0; + uint8_t mChannel = 0; + uint8_t mReceiveAtChannel = 0; uint8_t mFlash[kFlashSwapSize * kFlashSwapNum]; std::map>> mSettings; }; +template <> inline void FakePlatform::HandleSchedule<&FakePlatform::mMilliAlarmStart>() +{ + otPlatAlarmMilliFired(mInstance); +} + +#if OPENTHREAD_CONFIG_PLATFORM_USEC_TIMER_ENABLE +template <> inline void FakePlatform::HandleSchedule<&FakePlatform::mMicroAlarmStart>() +{ + otPlatAlarmMicroFired(mInstance); +} +#endif + } // namespace ot #endif // OT_GTEST_FAKE_PLATFORM_HPP_ diff --git a/tests/gtest/radio_spinel_rcp_test.cpp b/tests/gtest/radio_spinel_rcp_test.cpp index 4865b14b8..74ab0c236 100644 --- a/tests/gtest/radio_spinel_rcp_test.cpp +++ b/tests/gtest/radio_spinel_rcp_test.cpp @@ -285,3 +285,31 @@ TEST(RadioSpinelTransmit, shouldNotCauseSwitchingToRxAfterTxDoneIfNotRxOnWhenIdl platform.GoInMs(1000); EXPECT_EQ(platform.GetReceiveChannel(), 11); } + +TEST(RadioSpinelReceiveAt, shouldReceiveAtGiveRadioTime) +{ + class MockPlatform : public FakeCoprocessorPlatform + { + public: + MOCK_METHOD(otError, ReceiveAt, (uint8_t aChannel, uint32_t aStart, uint32_t aDuration), (override)); + }; + + MockPlatform platform; + + ON_CALL(platform, ReceiveAt) + .WillByDefault([&platform](uint8_t aChannel, uint32_t aStart, uint32_t aDuration) -> otError { + return platform.FakePlatform::ReceiveAt(aChannel, aStart, aDuration); + }); + + EXPECT_CALL(platform, ReceiveAt).Times(1); + + ASSERT_EQ(platform.mRadioSpinel.Enable(FakePlatform::CurrentInstance()), kErrorNone); + ASSERT_EQ(platform.mRadioSpinel.SetRxOnWhenIdle(false), kErrorNone); + ASSERT_EQ(platform.mRadioSpinel.ReceiveAt(100000, 10000, 11), kErrorNone); + platform.GoInUs(100000); + EXPECT_EQ(platform.GetReceiveChannel(), 0); + platform.GoInUs(1); + EXPECT_EQ(platform.GetReceiveChannel(), 11); + platform.GoInUs(10000); + EXPECT_EQ(platform.GetReceiveChannel(), 0); +}