[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.
This commit is contained in:
Zhanglong Xia
2023-04-12 11:11:21 -07:00
committed by GitHub
parent 3d39d24a21
commit e7a92f6aaa
9 changed files with 114 additions and 76 deletions
+2 -1
View File
@@ -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,
+44 -30
View File
@@ -230,23 +230,7 @@ void RadioSpinel<InterfaceType, ProcessContextType>::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 <typename InterfaceType, typename ProcessContextType>
void RadioSpinel<InterfaceType, ProcessContextType>::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 <typename InterfaceType, typename ProcessContextType>
otError RadioSpinel<InterfaceType, ProcessContextType>::CheckSpinelVersion(void)
{
@@ -1699,7 +1720,7 @@ otError RadioSpinel<InterfaceType, ProcessContextType>::Remove(spinel_prop_key_t
}
template <typename InterfaceType, typename ProcessContextType>
otError RadioSpinel<InterfaceType, ProcessContextType>::WaitResponse(void)
otError RadioSpinel<InterfaceType, ProcessContextType>::WaitResponse(bool aHandleRcpTimeout)
{
uint64_t end = otPlatTimeGet() + kMaxWaitTime * US_PER_MS;
@@ -1713,7 +1734,10 @@ otError RadioSpinel<InterfaceType, ProcessContextType>::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<InterfaceType, ProcessContextType>::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;
+18
View File
@@ -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<kMaxFrameSize> 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
+7 -2
View File
@@ -60,6 +60,7 @@
#include <openthread/logging.h>
#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;
}
+11 -8
View File
@@ -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.
*
+18 -7
View File
@@ -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<useconds_t>(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<useconds_t>(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);
+6 -9
View File
@@ -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);
+5 -11
View File
@@ -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.
@@ -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.