From e7a92f6aaa667e6cefb26b544a2a7472f7b58b71 Mon Sep 17 00:00:00 2001 From: Zhanglong Xia Date: Thu, 13 Apr 2023 02:11:21 +0800 Subject: [PATCH] [posix] unify the RCP reset sequence (#8858) This commit unifies the RCP reset sequence on the HDLC and SPI interfaces. The host will try the software reset first, and then try the hardware reset if the software reset fails or allowed. Reasons for unifying the RCP reset sequence: - The parameters of the Thread stack are fixed on Android. Developers can't use different parameters to specify SPI or HDLC interfaces to use different methods to reset RCP. - If the Thread stack and the BLE stack are running on the same radio chip, the hardware reset chip will cause the BLE stack to crash. The host needs to software reset RCP first, and try the hardware reset if the software reset fails. This can reduce the impact on the BLE stack. --- src/lib/spinel/radio_spinel.hpp | 3 +- src/lib/spinel/radio_spinel_impl.hpp | 74 +++++++++++-------- src/lib/spinel/spinel_interface.hpp | 18 +++++ src/posix/platform/hdlc_interface.cpp | 9 ++- src/posix/platform/hdlc_interface.hpp | 19 +++-- src/posix/platform/spi_interface.cpp | 25 +++++-- src/posix/platform/spi_interface.hpp | 15 ++-- src/posix/platform/vendor_interface.hpp | 16 ++-- .../platform/vendor_interface_example.cpp | 11 +-- 9 files changed, 114 insertions(+), 76 deletions(-) diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp index 732690701..d14f7c0aa 100644 --- a/src/lib/spinel/radio_spinel.hpp +++ b/src/lib/spinel/radio_spinel.hpp @@ -962,6 +962,7 @@ private: static void HandleReceivedFrame(void *aContext); + void ResetRcp(bool aResetRadio); otError CheckSpinelVersion(void); otError CheckRadioCapabilities(void); otError CheckRcpApiVersion(bool aSupportsRcpApiVersion, bool aSupportsMinHostRcpApiVersion); @@ -998,7 +999,7 @@ private: spinel_prop_key_t aKey, const char *aFormat, va_list aArgs); - otError WaitResponse(void); + otError WaitResponse(bool aHandleRcpTimeout = true); otError SendCommand(uint32_t aCommand, spinel_prop_key_t aKey, spinel_tid_t aTid, diff --git a/src/lib/spinel/radio_spinel_impl.hpp b/src/lib/spinel/radio_spinel_impl.hpp index 4bdc033ac..c9c676576 100644 --- a/src/lib/spinel/radio_spinel_impl.hpp +++ b/src/lib/spinel/radio_spinel_impl.hpp @@ -230,23 +230,7 @@ void RadioSpinel::Init(bool aResetRadio, mResetRadioOnStartup = aResetRadio; #endif - if (aResetRadio) - { - SuccessOrExit(error = SendReset(SPINEL_RESET_STACK)); - SuccessOrDie(mSpinelInterface.ResetConnection()); - } - - SuccessOrExit(error = WaitResponse()); - -#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 - while (mRcpFailed) - { - RecoverFromRcpFailure(); - } -#endif - - VerifyOrExit(mIsReady, error = OT_ERROR_FAILED); - + ResetRcp(aResetRadio); SuccessOrExit(error = CheckSpinelVersion()); SuccessOrExit(error = Get(SPINEL_PROP_NCP_VERSION, SPINEL_DATATYPE_UTF8_S, mVersion, sizeof(mVersion))); SuccessOrExit(error = Get(SPINEL_PROP_HWADDR, SPINEL_DATATYPE_EUI64_S, mIeeeEui64.m8)); @@ -279,6 +263,43 @@ exit: SuccessOrDie(error); } +template +void RadioSpinel::ResetRcp(bool aResetRadio) +{ + bool hardwareReset; + bool resetDone = false; + + mIsReady = false; + mWaitingKey = SPINEL_PROP_LAST_STATUS; + + if (aResetRadio && (SendReset(SPINEL_RESET_STACK) == OT_ERROR_NONE) && (WaitResponse(false) == OT_ERROR_NONE)) + { + otLogInfoPlat("Software reset RCP successfully"); + ExitNow(resetDone = true); + } + + hardwareReset = (mSpinelInterface.HardwareReset() == OT_ERROR_NONE); + SuccessOrExit(WaitResponse(false)); + + resetDone = true; + + if (hardwareReset) + { + otLogInfoPlat("Hardware reset RCP successfully"); + } + else + { + otLogInfoPlat("RCP self reset successfully"); + } + +exit: + if (!resetDone) + { + otLogCritPlat("Failed to reset RCP!"); + DieNow(OT_EXIT_FAILURE); + } +} + template otError RadioSpinel::CheckSpinelVersion(void) { @@ -1699,7 +1720,7 @@ otError RadioSpinel::Remove(spinel_prop_key_t } template -otError RadioSpinel::WaitResponse(void) +otError RadioSpinel::WaitResponse(bool aHandleRcpTimeout) { uint64_t end = otPlatTimeGet() + kMaxWaitTime * US_PER_MS; @@ -1713,7 +1734,10 @@ otError RadioSpinel::WaitResponse(void) if ((end <= now) || (mSpinelInterface.WaitForFrame(end - now) != OT_ERROR_NONE)) { otLogWarnPlat("Wait for response timeout"); - HandleRcpTimeout(); + if (aHandleRcpTimeout) + { + HandleRcpTimeout(); + } ExitNow(mError = OT_ERROR_NONE); } } while (mWaitingTid || !mIsReady); @@ -2305,24 +2329,14 @@ void RadioSpinel::RecoverFromRcpFailure(void) mState = kStateDisabled; mRxFrameBuffer.Clear(); - mSpinelInterface.OnRcpReset(); mCmdTidsInUse = 0; mCmdNextTid = 1; mTxRadioTid = 0; mWaitingTid = 0; - mWaitingKey = SPINEL_PROP_LAST_STATUS; mError = OT_ERROR_NONE; - mIsReady = false; mIsTimeSynced = false; - if (mResetRadioOnStartup) - { - SuccessOrDie(SendReset(SPINEL_RESET_STACK)); - SuccessOrDie(mSpinelInterface.ResetConnection()); - } - - SuccessOrDie(WaitResponse()); - + ResetRcp(mResetRadioOnStartup); SuccessOrDie(Set(SPINEL_PROP_PHY_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); mState = kStateSleep; diff --git a/src/lib/spinel/spinel_interface.hpp b/src/lib/spinel/spinel_interface.hpp index 585f257aa..1feafddd4 100644 --- a/src/lib/spinel/spinel_interface.hpp +++ b/src/lib/spinel/spinel_interface.hpp @@ -36,6 +36,7 @@ #define POSIX_APP_SPINEL_INTERFACE_HPP_ #include "lib/hdlc/hdlc.hpp" +#include "lib/spinel/spinel.h" namespace ot { namespace Spinel { @@ -58,6 +59,23 @@ public: typedef Hdlc::MultiFrameBuffer RxFrameBuffer; typedef void (*ReceiveFrameCallback)(void *aContext); + + /** + * This method indicates whether or not the frame is the Spinel SPINEL_CMD_RESET frame. + * + * @param[in] aFrame A pointer to buffer containing the spinel frame. + * @param[in] aLength The length (number of bytes) in the frame. + * + * @retval true If the frame is a Spinel SPINEL_CMD_RESET frame. + * @retval false If the frame is not a Spinel SPINEL_CMD_RESET frame. + * + */ + static bool IsSpinelResetCommand(const uint8_t *aFrame, uint16_t aLength) + { + static constexpr uint8_t kSpinelResetCommand[] = {SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_RESET}; + return (aLength >= sizeof(kSpinelResetCommand)) && + (memcmp(aFrame, kSpinelResetCommand, sizeof(kSpinelResetCommand)) == 0); + } }; } // namespace Spinel } // namespace ot diff --git a/src/posix/platform/hdlc_interface.cpp b/src/posix/platform/hdlc_interface.cpp index 9315bab4c..a37427e57 100644 --- a/src/posix/platform/hdlc_interface.cpp +++ b/src/posix/platform/hdlc_interface.cpp @@ -60,6 +60,7 @@ #include #include "common/code_utils.hpp" +#include "lib/spinel/spinel.h" #ifdef __APPLE__ @@ -139,8 +140,6 @@ HdlcInterface::HdlcInterface(SpinelInterface::ReceiveFrameCallback aCallback, mInterfaceMetrics.mRcpInterfaceType = OT_POSIX_RCP_BUS_UART; } -void HdlcInterface::OnRcpReset(void) { mHdlcDecoder.Reset(); } - otError HdlcInterface::Init(const Url::Url &aRadioUrl) { otError error = OT_ERROR_NONE; @@ -210,6 +209,12 @@ otError HdlcInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength) error = Write(encoderBuffer.GetFrame(), encoderBuffer.GetLength()); exit: + if ((error == OT_ERROR_NONE) && ot::Spinel::SpinelInterface::IsSpinelResetCommand(aFrame, aLength)) + { + mHdlcDecoder.Reset(); + error = ResetConnection(); + } + return error; } diff --git a/src/posix/platform/hdlc_interface.hpp b/src/posix/platform/hdlc_interface.hpp index f2d580127..dc9fbf89a 100644 --- a/src/posix/platform/hdlc_interface.hpp +++ b/src/posix/platform/hdlc_interface.hpp @@ -158,16 +158,13 @@ public: uint32_t GetBusSpeed(void) const { return mBaudRate; } /** - * This method is called when RCP failure detected and resets internal states of the interface. + * This method hardware resets the RCP. + * + * @retval OT_ERROR_NONE Successfully reset the RCP. + * @retval OT_ERROR_NOT_IMPLEMENT The hardware reset is not implemented. * */ - void OnRcpReset(void); - - /** - * This method is called when RCP is reset to recreate the connection with it. - * - */ - otError ResetConnection(void); + otError HardwareReset(void) { return OT_ERROR_NOT_IMPLEMENTED; } /** * This method returns the RCP interface metrics. @@ -178,6 +175,12 @@ public: const otRcpInterfaceMetrics *GetRcpInterfaceMetrics(void) const { return &mInterfaceMetrics; } private: + /** + * This method is called when RCP is reset to recreate the connection with it. + * + */ + otError ResetConnection(void); + /** * This method instructs `HdlcInterface` to read and decode data from radio over the socket. * diff --git a/src/posix/platform/spi_interface.cpp b/src/posix/platform/spi_interface.cpp index 73ed45c1a..d3bf52e36 100644 --- a/src/posix/platform/spi_interface.cpp +++ b/src/posix/platform/spi_interface.cpp @@ -85,7 +85,7 @@ SpiInterface::SpiInterface(SpinelInterface::ReceiveFrameCallback aCallback, { } -void SpiInterface::OnRcpReset(void) +void SpiInterface::ResetStates(void) { mSpiTxIsReady = false; mSpiTxRefusedCount = 0; @@ -95,9 +95,20 @@ void SpiInterface::OnRcpReset(void) memset(mSpiTxFrameBuffer, 0, sizeof(mSpiTxFrameBuffer)); memset(&mInterfaceMetrics, 0, sizeof(mInterfaceMetrics)); mInterfaceMetrics.mRcpInterfaceType = OT_POSIX_RCP_BUS_SPI; +} +otError SpiInterface::HardwareReset(void) +{ + ResetStates(); TriggerReset(); + + // If the `INT` pin is set to low during the restart of the RCP chip, which triggers continuous invalid SPI + // transactions by the host, it will cause the function `PushPullSpi()` to output lots of invalid warn log + // messages. Adding the delay here is used to wait for the RCP chip starts up to avoid outputing invalid + // log messages. usleep(static_cast(mSpiResetDelay) * kUsecPerMsec); + + return OT_ERROR_NONE; } otError SpiInterface::Init(const Url::Url &aRadioUrl) @@ -182,12 +193,6 @@ otError SpiInterface::Init(const Url::Url &aRadioUrl) InitResetPin(spiGpioResetDevice, spiGpioResetLine); InitSpiDev(aRadioUrl.GetPath(), spiMode, spiSpeed); - // Reset RCP chip. - TriggerReset(); - - // Waiting for the RCP chip starts up. - usleep(static_cast(spiResetDelay) * kUsecPerMsec); - return OT_ERROR_NONE; } @@ -800,6 +805,12 @@ otError SpiInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength) otError error = OT_ERROR_NONE; VerifyOrExit(aLength < (kMaxFrameSize - kSpiFrameHeaderSize), error = OT_ERROR_NO_BUFS); + + if (ot::Spinel::SpinelInterface::IsSpinelResetCommand(aFrame, aLength)) + { + ResetStates(); + } + VerifyOrExit(!mSpiTxIsReady, error = OT_ERROR_BUSY); memcpy(&mSpiTxFrameBuffer[kSpiFrameHeaderSize], aFrame, aLength); diff --git a/src/posix/platform/spi_interface.hpp b/src/posix/platform/spi_interface.hpp index 988d13fe3..0f3242e35 100644 --- a/src/posix/platform/spi_interface.hpp +++ b/src/posix/platform/spi_interface.hpp @@ -147,17 +147,13 @@ public: uint32_t GetBusSpeed(void) const { return ((mSpiDevFd >= 0) ? mSpiSpeedHz : 0); } /** - * This method is called when RCP failure detected and resets internal states of the interface. + * This method hardware resets the RCP. + * + * @retval OT_ERROR_NONE Successfully reset the RCP. + * @retval OT_ERROR_NOT_IMPLEMENT The hardware reset is not implemented. * */ - void OnRcpReset(void); - - /** - * This method is called when RCP is reset to recreate the connection with it. - * Intentionally empty. - * - */ - otError ResetConnection(void) { return OT_ERROR_NONE; } + otError HardwareReset(void); /** * This method returns the RCP interface metrics. @@ -168,6 +164,7 @@ public: const otRcpInterfaceMetrics *GetRcpInterfaceMetrics(void) const { return &mInterfaceMetrics; } private: + void ResetStates(void); int SetupGpioHandle(int aFd, uint8_t aLine, uint32_t aHandleFlags, const char *aLabel); int SetupGpioEvent(int aFd, uint8_t aLine, uint32_t aHandleFlags, uint32_t aEventFlags, const char *aLabel); void SetGpioValue(int aFd, uint8_t aValue); diff --git a/src/posix/platform/vendor_interface.hpp b/src/posix/platform/vendor_interface.hpp index 4b2011c55..8c82c204d 100644 --- a/src/posix/platform/vendor_interface.hpp +++ b/src/posix/platform/vendor_interface.hpp @@ -144,19 +144,13 @@ public: uint32_t GetBusSpeed(void) const; /** - * This method is called when RCP failure detected and resets internal states of the interface. + * This method hardware resets the RCP. + * + * @retval OT_ERROR_NONE Successfully reset the RCP. + * @retval OT_ERROR_NOT_IMPLEMENT The hardware reset is not implemented. * */ - void OnRcpReset(void); - - /** - * This method is called when RCP is reset to recreate the connection with it. - * - * @retval OT_ERROR_NONE Reset the connection successfully. - * @retval OT_ERROR_FAILED Failed to reset the connection. - * - */ - otError ResetConnection(void); + otError HardwareReset(void); /** * This method returns the RCP interface metrics. diff --git a/src/posix/platform/vendor_interface_example.cpp b/src/posix/platform/vendor_interface_example.cpp index 6ca5f0a13..ebf6b7869 100644 --- a/src/posix/platform/vendor_interface_example.cpp +++ b/src/posix/platform/vendor_interface_example.cpp @@ -101,9 +101,11 @@ void VendorInterface::Deinit(void) uint32_t VendorInterface::GetBusSpeed(void) const { return 1000000; } -void VendorInterface::OnRcpReset(void) +otError VendorInterface::HardwareReset(void) { // TODO: Implement vendor code here. + + return OT_ERROR_NOT_IMPLEMENTED; } void VendorInterface::UpdateFdSet(fd_set &aReadFdSet, fd_set &aWriteFdSet, int &aMaxFd, struct timeval &aTimeout) @@ -142,13 +144,6 @@ otError VendorInterface::SendFrame(const uint8_t *aFrame, uint16_t aLength) return OT_ERROR_NONE; } -otError VendorInterface::ResetConnection(void) -{ - // TODO: Implement vendor code here. - - return OT_ERROR_NONE; -} - const otRcpInterfaceMetrics *VendorInterface::GetRcpInterfaceMetrics(void) { // TODO: Implement vendor code here.