diff --git a/src/lib/spinel/BUILD.gn b/src/lib/spinel/BUILD.gn index e3911c085..09b2d39d9 100644 --- a/src/lib/spinel/BUILD.gn +++ b/src/lib/spinel/BUILD.gn @@ -45,6 +45,8 @@ spinel_sources = [ "spinel_buffer.hpp", "spinel_decoder.cpp", "spinel_decoder.hpp", + "spinel_driver.cpp", + "spinel_driver.hpp", "spinel_encoder.cpp", "spinel_encoder.hpp", "spinel_platform.h", diff --git a/src/lib/spinel/CMakeLists.txt b/src/lib/spinel/CMakeLists.txt index 1c21d6e84..5ff22c0f5 100644 --- a/src/lib/spinel/CMakeLists.txt +++ b/src/lib/spinel/CMakeLists.txt @@ -93,6 +93,7 @@ target_sources(openthread-radio-spinel PRIVATE logger.cpp radio_spinel.cpp + spinel_driver.cpp ) target_sources(openthread-spinel-ncp PRIVATE ${COMMON_SOURCES}) target_sources(openthread-spinel-rcp PRIVATE ${COMMON_SOURCES}) diff --git a/src/lib/spinel/radio_spinel.cpp b/src/lib/spinel/radio_spinel.cpp index c19e1ea9f..f17e3c70d 100644 --- a/src/lib/spinel/radio_spinel.cpp +++ b/src/lib/spinel/radio_spinel.cpp @@ -48,6 +48,7 @@ #include "lib/platform/exit_code.h" #include "lib/spinel/logger.hpp" #include "lib/spinel/spinel_decoder.hpp" +#include "lib/spinel/spinel_driver.hpp" namespace ot { namespace Spinel { @@ -65,22 +66,6 @@ bool RadioSpinel::sSupportsResetToBootloader = false; ///< RCP supports resettin otRadioCaps RadioSpinel::sRadioCaps = OT_RADIO_CAPS_NONE; -inline bool RadioSpinel::IsFrameForUs(spinel_iid_t aIid) -{ - bool found = false; - - for (spinel_iid_t iid : mIidList) - { - if (aIid == iid) - { - ExitNow(found = true); - } - } - -exit: - return found; -} - RadioSpinel::RadioSpinel(void) : Logger("RadioSpinel") , mInstance(nullptr) @@ -129,7 +114,6 @@ RadioSpinel::RadioSpinel(void) , mVendorRestorePropertiesContext(nullptr) #endif { - memset(mIidList, SPINEL_HEADER_INVALID_IID, sizeof(mIidList)); memset(&mRadioSpinelMetrics, 0, sizeof(mRadioSpinelMetrics)); memset(&mCallbacks, 0, sizeof(mCallbacks)); } @@ -149,25 +133,17 @@ void RadioSpinel::Init(SpinelInterface &aSpinelInterface, #endif mSpinelInterface = &aSpinelInterface; - SuccessOrDie(mSpinelInterface->Init(HandleReceivedFrame, this, mRxFrameBuffer)); #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_TIME_SYNC_ENABLE memset(&mTxIeInfo, 0, sizeof(otRadioIeInfo)); mTxRadioFrame.mInfo.mTxInfo.mIeInfo = &mTxIeInfo; #endif - VerifyOrDie(aIidList != nullptr, OT_EXIT_INVALID_ARGUMENTS); - VerifyOrDie(aIidListLength != 0 && aIidListLength <= OT_ARRAY_LENGTH(mIidList), OT_EXIT_INVALID_ARGUMENTS); - mIid = aIidList[0]; - memset(mIidList, SPINEL_HEADER_INVALID_IID, sizeof(mIidList)); - memcpy(mIidList, aIidList, aIidListLength * sizeof(spinel_iid_t)); + mSpinelDriver.Init(aSpinelInterface, aResetRadio, aIidList, aIidListLength); + mSpinelDriver.SetFrameHandler(&HandleReceivedFrame, &HandleSavedFrame, this); - ResetRcp(aResetRadio); - SuccessOrExit(error = CheckSpinelVersion()); - SuccessOrExit(error = Get(SPINEL_PROP_NCP_VERSION, SPINEL_DATATYPE_UTF8_S, sVersion, sizeof(sVersion))); SuccessOrExit(error = Get(SPINEL_PROP_HWADDR, SPINEL_DATATYPE_EUI64_S, sIeeeEui64.m8)); - - VerifyOrDie(IsRcp(supportsRcpApiVersion, supportsRcpMinHostApiVersion), OT_EXIT_RADIO_SPINEL_INCOMPATIBLE); + InitializeCaps(supportsRcpApiVersion, supportsRcpMinHostApiVersion); if (!aSkipRcpCompatibilityCheck) { @@ -197,50 +173,6 @@ void RadioSpinel::SetCallbacks(const struct RadioSpinelCallbacks &aCallbacks) mCallbacks = aCallbacks; } -void RadioSpinel::ResetRcp(bool aResetRadio) -{ - bool hardwareReset; - bool resetDone = false; - - // Avoid resetting the device twice in a row in Multipan RCP architecture - VerifyOrExit(!sIsReady, resetDone = true); - - mWaitingKey = SPINEL_PROP_LAST_STATUS; - - if (aResetRadio && (SendReset(SPINEL_RESET_STACK) == OT_ERROR_NONE) && (!sIsReady) && - (WaitResponse(false) == OT_ERROR_NONE)) - { - VerifyOrExit(sIsReady, resetDone = false); - LogInfo("Software reset RCP successfully"); - ExitNow(resetDone = true); - } - - hardwareReset = (mSpinelInterface->HardwareReset() == OT_ERROR_NONE); - - if (hardwareReset) - { - SuccessOrExit(WaitResponse(false)); - } - - resetDone = true; - - if (hardwareReset) - { - LogInfo("Hardware reset RCP successfully"); - } - else - { - LogInfo("RCP self reset successfully"); - } - -exit: - if (!resetDone) - { - LogCrit("Failed to reset RCP!"); - DieNow(OT_EXIT_FAILURE); - } -} - otError RadioSpinel::CheckSpinelVersion(void) { otError error = OT_ERROR_NONE; @@ -263,68 +195,23 @@ exit: return error; } -bool RadioSpinel::IsRcp(bool &aSupportsRcpApiVersion, bool &aSupportsRcpMinHostApiVersion) +void RadioSpinel::InitializeCaps(bool &aSupportsRcpApiVersion, bool &aSupportsRcpMinHostApiVersion) { - uint8_t capsBuffer[kCapsBufferSize]; - const uint8_t *capsData = capsBuffer; - spinel_size_t capsLength = sizeof(capsBuffer); - bool supportsRawRadio = false; - bool isRcp = false; - - aSupportsRcpApiVersion = false; - aSupportsRcpMinHostApiVersion = false; - - SuccessOrDie(Get(SPINEL_PROP_CAPS, SPINEL_DATATYPE_DATA_S, capsBuffer, &capsLength)); - - while (capsLength > 0) + if (!mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_CONFIG_RADIO)) { - unsigned int capability; - spinel_ssize_t unpacked; - - unpacked = spinel_datatype_unpack(capsData, capsLength, SPINEL_DATATYPE_UINT_PACKED_S, &capability); - VerifyOrDie(unpacked > 0, OT_EXIT_RADIO_SPINEL_INCOMPATIBLE); - - if (capability == SPINEL_CAP_MAC_RAW) - { - supportsRawRadio = true; - } - - if (capability == SPINEL_CAP_CONFIG_RADIO) - { - isRcp = true; - } - - if (capability == SPINEL_CAP_OPENTHREAD_LOG_METADATA) - { - sSupportsLogStream = true; - } - - if (capability == SPINEL_CAP_RCP_API_VERSION) - { - aSupportsRcpApiVersion = true; - } - - if (capability == SPINEL_CAP_RCP_RESET_TO_BOOTLOADER) - { - sSupportsResetToBootloader = true; - } - - if (capability == SPINEL_PROP_RCP_MIN_HOST_API_VERSION) - { - aSupportsRcpMinHostApiVersion = true; - } - - capsData += unpacked; - capsLength -= static_cast(unpacked); + LogCrit("The co-processor isn't a RCP!"); + DieNow(OT_EXIT_RADIO_SPINEL_INCOMPATIBLE); } - - if (!supportsRawRadio && isRcp) + if (!mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_MAC_RAW)) { LogCrit("RCP capability list does not include support for radio/raw mode"); DieNow(OT_EXIT_RADIO_SPINEL_INCOMPATIBLE); } - return isRcp; + sSupportsLogStream = mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_OPENTHREAD_LOG_METADATA); + aSupportsRcpApiVersion = mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_RCP_API_VERSION); + sSupportsResetToBootloader = mSpinelDriver.CoprocessorHasCap(SPINEL_CAP_RCP_RESET_TO_BOOTLOADER); + aSupportsRcpMinHostApiVersion = mSpinelDriver.CoprocessorHasCap(SPINEL_PROP_RCP_MIN_HOST_API_VERSION); } otError RadioSpinel::CheckRadioCapabilities(void) @@ -421,53 +308,10 @@ void RadioSpinel::Deinit(void) } // This allows implementing pseudo reset. - sIsReady = false; new (this) RadioSpinel(); } -void RadioSpinel::HandleReceivedFrame(void *aContext) { static_cast(aContext)->HandleReceivedFrame(); } - -void RadioSpinel::HandleReceivedFrame(void) -{ - otError error = OT_ERROR_NONE; - uint8_t header; - spinel_ssize_t unpacked; - - LogSpinelFrame(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), false); - unpacked = spinel_datatype_unpack(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), "C", &header); - - // Accept spinel messages with the correct IID or broadcast IID. - spinel_iid_t iid = SPINEL_HEADER_GET_IID(header); - - if (!IsFrameForUs(iid)) - { - mRxFrameBuffer.DiscardFrame(); - ExitNow(); - } - - VerifyOrExit(unpacked > 0 && (header & SPINEL_HEADER_FLAG) == SPINEL_HEADER_FLAG, error = OT_ERROR_PARSE); - - if (SPINEL_HEADER_GET_TID(header) == 0) - { - HandleNotification(mRxFrameBuffer); - } - else - { - HandleResponse(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength()); - mRxFrameBuffer.DiscardFrame(); - } - -exit: - if (error != OT_ERROR_NONE) - { - mRxFrameBuffer.DiscardFrame(); - LogWarn("Error handling hdlc frame: %s", otThreadErrorToString(error)); - } - - UpdateParseErrorCount(error); -} - -void RadioSpinel::HandleNotification(SpinelInterface::RxFrameBuffer &aFrameBuffer) +void RadioSpinel::HandleNotification(const uint8_t *aFrame, uint16_t aLength, bool &aShouldSaveFrame) { spinel_prop_key_t key; spinel_size_t len = 0; @@ -475,11 +319,12 @@ void RadioSpinel::HandleNotification(SpinelInterface::RxFrameBuffer &aFrameBuffe uint8_t *data = nullptr; uint32_t cmd; uint8_t header; - otError error = OT_ERROR_NONE; - bool shouldSaveFrame = false; + otError error = OT_ERROR_NONE; + + aShouldSaveFrame = false; + + unpacked = spinel_datatype_unpack(aFrame, aLength, "CiiD", &header, &cmd, &key, &data, &len); - unpacked = spinel_datatype_unpack(aFrameBuffer.GetFrame(), aFrameBuffer.GetLength(), "CiiD", &header, &cmd, &key, - &data, &len); VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); VerifyOrExit(SPINEL_HEADER_GET_TID(header) == 0, error = OT_ERROR_PARSE); @@ -492,7 +337,7 @@ void RadioSpinel::HandleNotification(SpinelInterface::RxFrameBuffer &aFrameBuffe if (!IsSafeToHandleNow(key)) { - ExitNow(shouldSaveFrame = true); + ExitNow(aShouldSaveFrame = true); } HandleValueIs(key, data, static_cast(len)); @@ -508,15 +353,6 @@ void RadioSpinel::HandleNotification(SpinelInterface::RxFrameBuffer &aFrameBuffe } exit: - if (!shouldSaveFrame || aFrameBuffer.SaveFrame() != OT_ERROR_NONE) - { - aFrameBuffer.DiscardFrame(); - - if (shouldSaveFrame) - { - LogCrit("RX Spinel buffer full, dropped incoming frame"); - } - } UpdateParseErrorCount(error); LogIfFail("Error processing notification", error); @@ -678,7 +514,8 @@ void RadioSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, } // this clear is necessary in case the RCP has sent messages between disable and reset - mRxFrameBuffer.Clear(); + mSpinelDriver.ClearRxBuffer(); + mSpinelDriver.SetCoprocessorReady(); LogInfo("RCP reset: %s", spinel_status_to_cstr(status)); sIsReady = true; @@ -782,6 +619,20 @@ void RadioSpinel::SetVendorRestorePropertiesCallback(otRadioSpinelVendorRestoreP } #endif +otError RadioSpinel::SendReset(uint8_t aResetType) +{ + otError error; + + if ((aResetType == SPINEL_RESET_BOOTLOADER) && !sSupportsResetToBootloader) + { + ExitNow(error = OT_ERROR_NOT_CAPABLE); + } + error = mSpinelDriver.SendReset(aResetType); + +exit: + return error; +} + otError RadioSpinel::ParseRadioFrame(otRadioFrame &aFrame, const uint8_t *aBuffer, uint16_t aLength, @@ -862,19 +713,6 @@ exit: return error; } -void RadioSpinel::ProcessFrameQueue(void) -{ - uint8_t *frame = nullptr; - uint16_t length; - - while (mRxFrameBuffer.GetNextSavedFrame(frame, length) == OT_ERROR_NONE) - { - HandleNotification(frame, length); - } - - mRxFrameBuffer.ClearSavedFrames(); -} - void RadioSpinel::RadioReceive(void) { if (!mIsPromiscuous) @@ -939,20 +777,7 @@ void RadioSpinel::ProcessRadioStateMachine(void) void RadioSpinel::Process(const void *aContext) { - if (mRxFrameBuffer.HasSavedFrame()) - { - ProcessFrameQueue(); - RecoverFromRcpFailure(); - } - - mSpinelInterface->Process(aContext); - RecoverFromRcpFailure(); - - if (mRxFrameBuffer.HasSavedFrame()) - { - ProcessFrameQueue(); - RecoverFromRcpFailure(); - } + mSpinelDriver.Process(aContext); ProcessRadioStateMachine(); RecoverFromRcpFailure(); @@ -1546,7 +1371,7 @@ otError RadioSpinel::WaitResponse(bool aHandleRcpTimeout) } ExitNow(mError = OT_ERROR_RESPONSE_TIMEOUT); } - } while (mWaitingTid || !sIsReady); + } while (mWaitingTid); LogIfFail("Error waiting response", mError); // This indicates end of waiting response. @@ -1580,65 +1405,6 @@ exit: return tid; } -otError RadioSpinel::SendReset(uint8_t aResetType) -{ - otError error = OT_ERROR_NONE; - uint8_t buffer[kMaxSpinelFrame]; - spinel_ssize_t packed; - - if ((aResetType == SPINEL_RESET_BOOTLOADER) && !sSupportsResetToBootloader) - { - ExitNow(error = OT_ERROR_NOT_CAPABLE); - } - - // Pack the header, command and key - packed = spinel_datatype_pack(buffer, sizeof(buffer), SPINEL_DATATYPE_COMMAND_S SPINEL_DATATYPE_UINT8_S, - SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid), SPINEL_CMD_RESET, aResetType); - - VerifyOrExit(packed > 0 && static_cast(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); - - SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, static_cast(packed))); - LogSpinelFrame(buffer, static_cast(packed), true); - -exit: - return error; -} - -otError RadioSpinel::SendCommand(uint32_t aCommand, - spinel_prop_key_t aKey, - spinel_tid_t tid, - const char *aFormat, - va_list args) -{ - otError error = OT_ERROR_NONE; - uint8_t buffer[kMaxSpinelFrame]; - spinel_ssize_t packed; - uint16_t offset; - - // Pack the header, command and key - packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | tid, - aCommand, aKey); - - VerifyOrExit(packed > 0 && static_cast(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); - - offset = static_cast(packed); - - // Pack the data (if any) - if (aFormat) - { - packed = spinel_datatype_vpack(buffer + offset, sizeof(buffer) - offset, aFormat, args); - VerifyOrExit(packed > 0 && static_cast(packed + offset) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); - - offset += static_cast(packed); - } - - SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, offset)); - LogSpinelFrame(buffer, offset, true); - -exit: - return error; -} - otError RadioSpinel::RequestV(uint32_t command, spinel_prop_key_t aKey, const char *aFormat, va_list aArgs) { otError error = OT_ERROR_NONE; @@ -1646,7 +1412,7 @@ otError RadioSpinel::RequestV(uint32_t command, spinel_prop_key_t aKey, const ch VerifyOrExit(tid > 0, error = OT_ERROR_BUSY); - error = SendCommand(command, aKey, tid, aFormat, aArgs); + error = mSpinelDriver.SendCommand(command, aKey, tid, aFormat, aArgs); SuccessOrExit(error); if (aKey == SPINEL_PROP_STREAM_RAW) @@ -2125,13 +1891,10 @@ void RadioSpinel::HandleRcpTimeout(void) #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 mRcpFailure = kRcpFailureTimeout; #else - if (!sIsReady) - { - LogCrit("Failed to communicate with RCP - no response from RCP during initialization"); - LogCrit("This is not a bug and typically due a config error (wrong URL parameters) or bad RCP image:"); - LogCrit("- Make sure RCP is running the correct firmware"); - LogCrit("- Double check the config parameters passed as `RadioURL` input"); - } + LogCrit("Failed to communicate with RCP - no response from RCP during initialization"); + LogCrit("This is not a bug and typically due a config error (wrong URL parameters) or bad RCP image:"); + LogCrit("- Make sure RCP is running the correct firmware"); + LogCrit("- Double check the config parameters passed as `RadioURL` input"); DieNow(OT_EXIT_RADIO_SPINEL_NO_RESPONSE); #endif @@ -2168,7 +1931,17 @@ void RadioSpinel::RecoverFromRcpFailure(void) LogWarn("Trying to recover (%d/%d)", mRcpFailureCount, kMaxFailureCount); mState = kStateDisabled; - mRxFrameBuffer.Clear(); + + mSpinelDriver.ClearRxBuffer(); + if (skipReset) + { + mSpinelDriver.SetCoprocessorReady(); + } + else + { + mSpinelDriver.ResetCoprocessor(mResetRadioOnStartup); + } + mCmdTidsInUse = 0; mCmdNextTid = 1; mTxRadioTid = 0; @@ -2176,15 +1949,6 @@ void RadioSpinel::RecoverFromRcpFailure(void) mError = OT_ERROR_NONE; mIsTimeSynced = false; - if (skipReset) - { - sIsReady = true; - } - else - { - ResetRcp(mResetRadioOnStartup); - } - SuccessOrDie(Set(SPINEL_PROP_PHY_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); mState = kStateSleep; @@ -2232,6 +1996,35 @@ exit: #endif // OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 } +void RadioSpinel::HandleReceivedFrame(const uint8_t *aFrame, + uint16_t aLength, + uint8_t aHeader, + bool &aSave, + void *aContext) +{ + static_cast(aContext)->HandleReceivedFrame(aFrame, aLength, aHeader, aSave); +} + +void RadioSpinel::HandleReceivedFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aShouldSaveFrame) +{ + if (SPINEL_HEADER_GET_TID(aHeader) == 0) + { + HandleNotification(aFrame, aLength, aShouldSaveFrame); + } + else + { + HandleResponse(aFrame, aLength); + aShouldSaveFrame = false; + } +} + +void RadioSpinel::HandleSavedFrame(const uint8_t *aFrame, uint16_t aLength, void *aContext) +{ + static_cast(aContext)->HandleSavedFrame(aFrame, aLength); +} + +void RadioSpinel::HandleSavedFrame(const uint8_t *aFrame, uint16_t aLength) { HandleNotification(aFrame, aLength); } + #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 void RadioSpinel::RestoreProperties(void) { diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp index 5b22432ac..d053a9296 100644 --- a/src/lib/spinel/radio_spinel.hpp +++ b/src/lib/spinel/radio_spinel.hpp @@ -41,22 +41,13 @@ #include "lib/spinel/logger.hpp" #include "lib/spinel/radio_spinel_metrics.h" #include "lib/spinel/spinel.h" +#include "lib/spinel/spinel_driver.hpp" #include "lib/spinel/spinel_interface.hpp" #include "ncp/ncp_config.h" namespace ot { namespace Spinel { -/** - * Maximum number of Spinel Interface IDs. - * - */ -#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE -static constexpr uint8_t kSpinelHeaderMaxNumIid = 4; -#else -static constexpr uint8_t kSpinelHeaderMaxNumIid = 1; -#endif - struct RadioSpinelCallbacks { /** @@ -349,14 +340,6 @@ public: */ otError SetFemLnaGain(int8_t aGain); - /** - * Returns the radio sw version string. - * - * @returns A pointer to the radio version string. - * - */ - const char *GetVersion(void) const { return sVersion; } - /** * Returns the radio capabilities. * @@ -724,14 +707,6 @@ public: */ uint32_t GetRadioChannelMask(bool aPreferred); - /** - * Processes a received Spinel frame. - * - * The newly received frame is available in `RxFrameBuffer` from `SpinelInterface::GetRxFrameBuffer()`. - * - */ - void HandleReceivedFrame(void); - /** * Sets MAC key and key index to RCP. * @@ -834,27 +809,13 @@ public: uint8_t GetCslUncertainty(void); #endif - /** - * Checks whether the spinel interface is radio-only. - * - * @param[out] aSupportsRcpApiVersion A reference to a boolean variable to update whether the list of - * spinel capabilities includes `SPINEL_CAP_RCP_API_VERSION`. - * @param[out] aSupportsRcpMinHostApiVersion A reference to a boolean variable to update whether the list of - * spinel capabilities includes `SPINEL_CAP_RCP_MIN_HOST_API_VERSION`. - * - * @retval TRUE The radio chip is in radio-only mode. - * @retval FALSE Otherwise. - * - */ - bool IsRcp(bool &aSupportsRcpApiVersion, bool &aSupportsRcpMinHostApiVersion); - /** * Checks whether there is pending frame in the buffer. * * @returns Whether there is pending frame in the buffer. * */ - bool HasPendingFrame(void) const { return mRxFrameBuffer.HasSavedFrame(); } + bool HasPendingFrame(void) const { return mSpinelDriver.HasPendingFrame(); } /** * Returns the next timepoint to recalculate RCP time offset. @@ -880,6 +841,14 @@ public: */ uint32_t GetBusSpeed(void) const; + /** + * Returns the co-processor sw version string. + * + * @returns A pointer to the co-processor version string. + * + */ + const char *GetVersion(void) const { return mSpinelDriver.GetVersion(); } + /** * Sets the max transmit power. * @@ -969,13 +938,13 @@ public: otError Remove(spinel_prop_key_t aKey, const char *aFormat, ...); /** - * Tries to reset the co-processor. + * Sends a reset command to the RCP. * - * @prarm[in] aResetType The reset type, SPINEL_RESET_PLATFORM, SPINEL_RESET_STACK, or SPINEL_RESET_BOOTLOADER. + * @param[in] aResetType The reset type, SPINEL_RESET_PLATFORM, SPINEL_RESET_STACK, or SPINEL_RESET_BOOTLOADER. * - * @retval OT_ERROR_NONE Successfully removed item from the property. + * @retval OT_ERROR_NONE Successfully sent the reset command. * @retval OT_ERROR_BUSY Failed due to another operation is on going. - * @retval OT_ERROR_NOT_CAPABLE Requested reset type is not supported by the co-processor + * @retval OT_ERROR_NOT_CAPABLE Requested reset type is not supported by the co-processor. * */ otError SendReset(uint8_t aResetType); @@ -1108,7 +1077,6 @@ public: private: enum { - kMaxSpinelFrame = SPINEL_FRAME_MAX_SIZE, kMaxWaitTime = 2000, ///< Max time to wait for response in milliseconds. kVersionStringSize = 128, ///< Max size of version string. kCapsBufferSize = 100, ///< Max buffer size used to store `SPINEL_PROP_CAPS` value. @@ -1133,12 +1101,10 @@ private: typedef otError (RadioSpinel::*ResponseHandler)(const uint8_t *aBuffer, uint16_t aLength); - static void HandleReceivedFrame(void *aContext); - - void ResetRcp(bool aResetRadio); otError CheckSpinelVersion(void); otError CheckRadioCapabilities(void); otError CheckRcpApiVersion(bool aSupportsRcpApiVersion, bool aSupportsRcpMinHostApiVersion); + void InitializeCaps(bool &aSupportsRcpApiVersion, bool &aSupportsRcpMinHostApiVersion); /** * Triggers a state transfer of the state machine. @@ -1173,11 +1139,6 @@ private: const char *aFormat, va_list aArgs); otError WaitResponse(bool aHandleRcpTimeout = true); - otError SendCommand(uint32_t aCommand, - spinel_prop_key_t aKey, - spinel_tid_t aTid, - const char *aFormat, - va_list aArgs); otError ParseRadioFrame(otRadioFrame &aFrame, const uint8_t *aBuffer, uint16_t aLength, spinel_ssize_t &aUnpacked); /** @@ -1196,18 +1157,7 @@ private: return !(aKey == SPINEL_PROP_STREAM_RAW || aKey == SPINEL_PROP_MAC_ENERGY_SCAN_RESULT); } - /** - * Checks whether given interface ID is part of list of IIDs to be allowed. - * - * @param[in] aIid Spinel Interface ID. - * - * @retval TRUE Given IID present in allow list. - * @retval FALSE Otherwise. - * - */ - inline bool IsFrameForUs(spinel_iid_t aIid); - - void HandleNotification(SpinelInterface::RxFrameBuffer &aFrameBuffer); + void HandleNotification(const uint8_t *aFrame, uint16_t aLength, bool &aShouldSaveFrame); void HandleNotification(const uint8_t *aFrame, uint16_t aLength); void HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength); @@ -1225,6 +1175,15 @@ private: void HandleRcpTimeout(void); void RecoverFromRcpFailure(void); + static void HandleReceivedFrame(const uint8_t *aFrame, + uint16_t aLength, + uint8_t aHeader, + bool &aSave, + void *aContext); + void HandleReceivedFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aShouldSaveFrame); + static void HandleSavedFrame(const uint8_t *aFrame, uint16_t aLength, void *aContext); + void HandleSavedFrame(const uint8_t *aFrame, uint16_t aLength); + void UpdateParseErrorCount(otError aError) { mRadioSpinelMetrics.mSpinelParseErrorCount += (aError == OT_ERROR_PARSE) ? 1 : 0; @@ -1291,7 +1250,8 @@ private: #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 - enum { + enum + { kRcpFailureNone, kRcpFailureTimeout, kRcpFailureUnexpectedReset, @@ -1347,6 +1307,8 @@ private: otRadioSpinelVendorRestorePropertiesCallback mVendorRestorePropertiesCallback; void *mVendorRestorePropertiesContext; #endif + + SpinelDriver mSpinelDriver; }; } // namespace Spinel diff --git a/src/lib/spinel/spinel_driver.cpp b/src/lib/spinel/spinel_driver.cpp new file mode 100644 index 000000000..dcf244531 --- /dev/null +++ b/src/lib/spinel/spinel_driver.cpp @@ -0,0 +1,466 @@ +/* + * Copyright (c) 2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spinel_driver.hpp" + +#include + +#include + +#include "common/code_utils.hpp" +#include "common/new.hpp" +#include "common/num_utils.hpp" +#include "lib/platform/exit_code.h" +#include "lib/spinel/spinel.h" + +namespace ot { +namespace Spinel { + +constexpr spinel_tid_t sTid = 1; ///< In Spinel Driver, only use Tid as 1. + +SpinelDriver::SpinelDriver(void) + : Logger("SpinelDriver") + , mSpinelInterface(nullptr) + , mWaitingKey(SPINEL_PROP_LAST_STATUS) + , mIsWaitingForResponse(false) + , mIid(SPINEL_HEADER_INVALID_IID) + , mSpinelVersionMajor(-1) + , mSpinelVersionMinor(-1) + , mIsCoprocessorReady(false) +{ + memset(mVersion, 0, sizeof(mVersion)); + + mReceivedFrameHandler = &HandleInitialFrame; + mFrameHandlerContext = this; +} + +void SpinelDriver::Init(SpinelInterface &aSpinelInterface, + bool aSoftwareReset, + const spinel_iid_t *aIidList, + uint8_t aIidListLength) +{ + mSpinelInterface = &aSpinelInterface; + mRxFrameBuffer.Clear(); + SuccessOrDie(mSpinelInterface->Init(HandleReceivedFrame, this, mRxFrameBuffer)); + + VerifyOrDie(aIidList != nullptr, OT_EXIT_INVALID_ARGUMENTS); + VerifyOrDie(aIidListLength != 0 && aIidListLength <= mIidList.GetMaxSize(), OT_EXIT_INVALID_ARGUMENTS); + + for (uint8_t i = 0; i < aIidListLength; i++) + { + SuccessOrDie(mIidList.PushBack(aIidList[i])); + } + mIid = aIidList[0]; + + ResetCoprocessor(aSoftwareReset); + SuccessOrDie(CheckSpinelVersion()); + SuccessOrDie(GetCoprocessorVersion()); + SuccessOrDie(GetCoprocessorCaps()); +} + +void SpinelDriver::Deinit(void) +{ + // This allows implementing pseudo reset. + new (this) SpinelDriver(); +} + +otError SpinelDriver::SendReset(uint8_t aResetType) +{ + otError error = OT_ERROR_NONE; + uint8_t buffer[kMaxSpinelFrame]; + spinel_ssize_t packed; + + // Pack the header, command and key + packed = spinel_datatype_pack(buffer, sizeof(buffer), SPINEL_DATATYPE_COMMAND_S SPINEL_DATATYPE_UINT8_S, + SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid), SPINEL_CMD_RESET, aResetType); + + VerifyOrExit(packed > 0 && static_cast(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); + + SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, static_cast(packed))); + LogSpinelFrame(buffer, static_cast(packed), true /* aTx */); + +exit: + return error; +} + +void SpinelDriver::ResetCoprocessor(bool aSoftwareReset) +{ + bool hardwareReset; + bool resetDone = false; + + // Avoid resetting the device twice in a row in Multipan RCP architecture + VerifyOrExit(!mIsCoprocessorReady, resetDone = true); + + mWaitingKey = SPINEL_PROP_LAST_STATUS; + + if (aSoftwareReset && (SendReset(SPINEL_RESET_STACK) == OT_ERROR_NONE) && (!mIsCoprocessorReady) && + (WaitResponse() == OT_ERROR_NONE)) + { + VerifyOrExit(mIsCoprocessorReady, resetDone = false); + LogCrit("Software reset co-processor successfully"); + ExitNow(resetDone = true); + } + + hardwareReset = (mSpinelInterface->HardwareReset() == OT_ERROR_NONE); + + if (hardwareReset) + { + SuccessOrExit(WaitResponse()); + } + + resetDone = true; + + if (hardwareReset) + { + LogInfo("Hardware reset co-processor successfully"); + } + else + { + LogInfo("co-processor self reset successfully"); + } + +exit: + if (!resetDone) + { + LogCrit("Failed to reset co-processor!"); + DieNow(OT_EXIT_FAILURE); + } +} + +void SpinelDriver::Process(const void *aContext) +{ + if (mRxFrameBuffer.HasSavedFrame()) + { + ProcessFrameQueue(); + } + + mSpinelInterface->Process(aContext); + + if (mRxFrameBuffer.HasSavedFrame()) + { + ProcessFrameQueue(); + } +} + +otError SpinelDriver::SendCommand(uint32_t aCommand, spinel_prop_key_t aKey, spinel_tid_t aTid) +{ + otError error = OT_ERROR_NONE; + uint8_t buffer[kMaxSpinelFrame]; + spinel_ssize_t packed; + uint16_t offset; + + // Pack the header, command and key + packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | aTid, + aCommand, aKey); + + VerifyOrExit(packed > 0 && static_cast(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); + + offset = static_cast(packed); + + SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, offset)); + +exit: + return error; +} + +otError SpinelDriver::SendCommand(uint32_t aCommand, + spinel_prop_key_t aKey, + spinel_tid_t aTid, + const char *aFormat, + va_list aArgs) +{ + otError error = OT_ERROR_NONE; + uint8_t buffer[kMaxSpinelFrame]; + spinel_ssize_t packed; + uint16_t offset; + + // Pack the header, command and key + packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | aTid, + aCommand, aKey); + + VerifyOrExit(packed > 0 && static_cast(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); + + offset = static_cast(packed); + + // Pack the data (if any) + if (aFormat) + { + packed = spinel_datatype_vpack(buffer + offset, sizeof(buffer) - offset, aFormat, aArgs); + VerifyOrExit(packed > 0 && static_cast(packed + offset) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); + + offset += static_cast(packed); + } + + SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, offset)); + +exit: + return error; +} + +void SpinelDriver::SetFrameHandler(ReceivedFrameHandler aReceivedFrameHandler, + SavedFrameHandler aSavedFrameHandler, + void *aContext) +{ + mReceivedFrameHandler = aReceivedFrameHandler; + mSavedFrameHandler = aSavedFrameHandler; + mFrameHandlerContext = aContext; +} + +otError SpinelDriver::WaitResponse(void) +{ + otError error = OT_ERROR_NONE; + uint64_t end = otPlatTimeGet() + kMaxWaitTime * kUsPerMs; + + LogDebg("Waiting response: key=%lu", ToUlong(mWaitingKey)); + + do + { + uint64_t now = otPlatTimeGet(); + + if ((end <= now) || (mSpinelInterface->WaitForFrame(end - now) != OT_ERROR_NONE)) + { + LogWarn("Wait for response timeout"); + ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT); + } + } while (mIsWaitingForResponse || !mIsCoprocessorReady); + + mWaitingKey = SPINEL_PROP_LAST_STATUS; + +exit: + return error; +} + +void SpinelDriver::HandleReceivedFrame(void *aContext) { static_cast(aContext)->HandleReceivedFrame(); } + +void SpinelDriver::HandleReceivedFrame(void) +{ + otError error = OT_ERROR_NONE; + uint8_t header; + spinel_ssize_t unpacked; + bool shouldSave = true; + spinel_iid_t iid; + + LogSpinelFrame(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), false); + unpacked = spinel_datatype_unpack(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), "C", &header); + + // Accept spinel messages with the correct IID or broadcast IID. + iid = SPINEL_HEADER_GET_IID(header); + + if (!mIidList.Contains(iid)) + { + mRxFrameBuffer.DiscardFrame(); + ExitNow(); + } + + VerifyOrExit(unpacked > 0 && (header & SPINEL_HEADER_FLAG) == SPINEL_HEADER_FLAG, error = OT_ERROR_PARSE); + + assert(mReceivedFrameHandler != nullptr && mFrameHandlerContext != nullptr); + mReceivedFrameHandler(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), header, shouldSave, + mFrameHandlerContext); + + if (shouldSave) + { + error = mRxFrameBuffer.SaveFrame(); + } + else + { + mRxFrameBuffer.DiscardFrame(); + } + +exit: + if (error != OT_ERROR_NONE) + { + mRxFrameBuffer.DiscardFrame(); + LogWarn("Error handling spinel frame: %s", otThreadErrorToString(error)); + } +} + +void SpinelDriver::HandleInitialFrame(const uint8_t *aFrame, + uint16_t aLength, + uint8_t aHeader, + bool &aSave, + void *aContext) +{ + static_cast(aContext)->HandleInitialFrame(aFrame, aLength, aHeader, aSave); +} + +void SpinelDriver::HandleInitialFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aSave) +{ + spinel_prop_key_t key; + uint8_t *data = nullptr; + spinel_size_t len = 0; + uint8_t header = 0; + uint32_t cmd = 0; + spinel_ssize_t rval = 0; + spinel_ssize_t unpacked; + otError error = OT_ERROR_NONE; + + OT_UNUSED_VARIABLE(aHeader); + + rval = spinel_datatype_unpack(aFrame, aLength, "CiiD", &header, &cmd, &key, &data, &len); + VerifyOrExit(rval > 0 && cmd >= SPINEL_CMD_PROP_VALUE_IS && cmd <= SPINEL_CMD_PROP_VALUE_REMOVED, + error = OT_ERROR_PARSE); + + VerifyOrExit(cmd == SPINEL_CMD_PROP_VALUE_IS, error = OT_ERROR_DROP); + + if (key == SPINEL_PROP_LAST_STATUS) + { + spinel_status_t status = SPINEL_STATUS_OK; + + unpacked = spinel_datatype_unpack(data, len, "i", &status); + VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); + + if (status >= SPINEL_STATUS_RESET__BEGIN && status <= SPINEL_STATUS_RESET__END) + { + // this clear is necessary in case the RCP has sent messages between disable and reset + mRxFrameBuffer.Clear(); + + LogInfo("co-processor reset: %s", spinel_status_to_cstr(status)); + mIsCoprocessorReady = true; + } + else + { + LogInfo("co-processor last status: %s", spinel_status_to_cstr(status)); + ExitNow(); + } + } + else + { + // Drop other frames when the key isn't waiting key. + VerifyOrExit(mWaitingKey == key, error = OT_ERROR_DROP); + + if (key == SPINEL_PROP_PROTOCOL_VERSION) + { + unpacked = spinel_datatype_unpack(data, len, (SPINEL_DATATYPE_UINT_PACKED_S SPINEL_DATATYPE_UINT_PACKED_S), + &mSpinelVersionMajor, &mSpinelVersionMinor); + + VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); + } + else if (key == SPINEL_PROP_NCP_VERSION) + { + unpacked = spinel_datatype_unpack_in_place(data, len, SPINEL_DATATYPE_UTF8_S, mVersion, sizeof(mVersion)); + + VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); + } + else if (key == SPINEL_PROP_CAPS) + { + uint8_t capsBuffer[kCapsBufferSize]; + spinel_size_t capsLength = sizeof(capsBuffer); + const uint8_t *capsData = capsBuffer; + + unpacked = spinel_datatype_unpack_in_place(data, len, SPINEL_DATATYPE_DATA_S, capsBuffer, &capsLength); + + VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); + + while (capsLength > 0) + { + unsigned int capability; + + unpacked = spinel_datatype_unpack(capsData, capsLength, SPINEL_DATATYPE_UINT_PACKED_S, &capability); + VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); + + SuccessOrExit(error = mCoprocessorCaps.PushBack(capability)); + + capsData += unpacked; + capsLength -= static_cast(unpacked); + } + } + + mIsWaitingForResponse = false; + } + +exit: + aSave = false; + LogIfFail("Error processing frame", error); +} + +otError SpinelDriver::CheckSpinelVersion(void) +{ + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_PROTOCOL_VERSION, sTid)); + mIsWaitingForResponse = true; + mWaitingKey = SPINEL_PROP_PROTOCOL_VERSION; + + SuccessOrExit(error = WaitResponse()); + + if ((mSpinelVersionMajor != SPINEL_PROTOCOL_VERSION_THREAD_MAJOR) || + (mSpinelVersionMinor != SPINEL_PROTOCOL_VERSION_THREAD_MINOR)) + { + LogCrit("Spinel version mismatch - Posix:%d.%d, co-processor:%d.%d", SPINEL_PROTOCOL_VERSION_THREAD_MAJOR, + SPINEL_PROTOCOL_VERSION_THREAD_MINOR, mSpinelVersionMajor, mSpinelVersionMinor); + DieNow(OT_EXIT_RADIO_SPINEL_INCOMPATIBLE); + } + +exit: + return error; +} + +otError SpinelDriver::GetCoprocessorVersion(void) +{ + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_NCP_VERSION, sTid)); + mIsWaitingForResponse = true; + mWaitingKey = SPINEL_PROP_NCP_VERSION; + + SuccessOrExit(error = WaitResponse()); +exit: + return error; +} + +otError SpinelDriver::GetCoprocessorCaps(void) +{ + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_CAPS, sTid)); + mIsWaitingForResponse = true; + mWaitingKey = SPINEL_PROP_CAPS; + + SuccessOrExit(error = WaitResponse()); +exit: + return error; +} + +void SpinelDriver::ProcessFrameQueue(void) +{ + uint8_t *frame = nullptr; + uint16_t length; + + assert(mSavedFrameHandler != nullptr && mFrameHandlerContext != nullptr); + + while (mRxFrameBuffer.GetNextSavedFrame(frame, length) == OT_ERROR_NONE) + { + mSavedFrameHandler(frame, length, mFrameHandlerContext); + } + + mRxFrameBuffer.ClearSavedFrames(); +} + +} // namespace Spinel +} // namespace ot diff --git a/src/lib/spinel/spinel_driver.hpp b/src/lib/spinel/spinel_driver.hpp new file mode 100644 index 000000000..fe412c1f1 --- /dev/null +++ b/src/lib/spinel/spinel_driver.hpp @@ -0,0 +1,306 @@ +/* + * Copyright (c) 2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef SPINEL_DRIVER_HPP_ +#define SPINEL_DRIVER_HPP_ + +#include + +#include "lib/spinel/logger.hpp" +#include "lib/spinel/spinel.h" +#include "lib/spinel/spinel_interface.hpp" + +namespace ot { +namespace Spinel { + +/** + * Maximum number of Spinel Interface IDs. + * + */ +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +static constexpr uint8_t kSpinelHeaderMaxNumIid = 4; +#else +static constexpr uint8_t kSpinelHeaderMaxNumIid = 1; +#endif + +class SpinelDriver : public Logger +{ +public: + typedef void ( + *ReceivedFrameHandler)(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aSave, void *aContext); + typedef void (*SavedFrameHandler)(const uint8_t *aFrame, uint16_t aLength, void *aContext); + + /** + * Constructor of the SpinelDriver. + * + */ + SpinelDriver(void); + + /** + * Initialize this SpinelDriver Instance. + * + * @param[in] aSpinelInterface A reference to the Spinel interface. + * @param[in] aSoftwareReset TRUE to reset on init, FALSE to not reset on init. + * @param[in] aIidList A Pointer to the list of IIDs to receive spinel frame from. + * First entry must be the IID of the Host Application. + * @param[in] aIidListLength The Length of the @p aIidList. + * + */ + void Init(SpinelInterface &aSpinelInterface, + bool aSoftwareReset, + const spinel_iid_t *aIidList, + uint8_t aIidListLength); + + /** + * Deinitialize this SpinelDriver Instance. + * + */ + void Deinit(void); + + /** + * Clear the rx frame buffer. + * + */ + void ClearRxBuffer(void) { mRxFrameBuffer.Clear(); } + + /** + * Set the internal state of co-processor as ready. + * + * This method is used to skip a reset. + */ + void SetCoprocessorReady(void) { mIsCoprocessorReady = true; } + + /** + * Send a reset command to the co-processor. + * + * @prarm[in] aResetType The reset type, SPINEL_RESET_PLATFORM, SPINEL_RESET_STACK, or SPINEL_RESET_BOOTLOADER. + * + * @retval OT_ERROR_NONE Successfully removed item from the property. + * @retval OT_ERROR_BUSY Failed due to another operation is on going. + * + */ + otError SendReset(uint8_t aResetType); + + /** + * Reset the co-processor. + * + * This method will reset the co-processor and wait until the co-process is ready (receiving SPINEL_PROP_LAST_STATUS + * from the it). The reset will be either a software or hardware reset. If `aSoftwareReset` is `true`, then the + * method will first try a software reset. If the software reset succeeds, the method exits. Otherwise the method + * will then try a hardware reset. If `aSoftwareReset` is `false`, then method will directly try a hardware reset. + * + * @param[in] aSoftwareReset TRUE to try SW reset first, FALSE to directly try HW reset. + * + */ + void ResetCoprocessor(bool aSoftwareReset); + + /** + * Processes any pending the I/O data. + * + * The method should be called by the system loop to process received spinel frames. + * + * @param[in] aContext The process context. + * + */ + void Process(const void *aContext); + + /** + * Checks whether there is pending frame in the buffer. + * + * The method is required by the system loop to update timer fd. + * + * @returns Whether there is pending frame in the buffer. + * + */ + bool HasPendingFrame(void) const { return mRxFrameBuffer.HasSavedFrame(); } + + /** + * Returns the co-processor sw version string. + * + * @returns A pointer to the co-processor version string. + * + */ + const char *GetVersion(void) const { return mVersion; } + + /* + * Sends a spinel command to the co-processor. + * + * @param[in] aCommand The spinel command. + * @param[in] aKey The spinel property key. + * @param[in] aTid The spinel transaction id. + * @param[in] aFormat The format string of the arguments to send. + * @param[in] aArgs The argument list. + * + * @retval OT_ERROR_NONE Successfully sent the command through spinel interface. + * @retval OT_ERROR_INVALID_STATE The spinel interface is in an invalid state. + * @retval OT_ERROR_NO_BUFS The spinel interface doesn't have enough buffer. + * + */ + otError SendCommand(uint32_t aCommand, + spinel_prop_key_t aKey, + spinel_tid_t aTid, + const char *aFormat, + va_list aArgs); + + /* + * Sets the handler to process the received spinel frame. + * + * @param[in] aReceivedFrameHandler The handler to process received spinel frames. + * @param[in] aSavedFrameHandler The handler to process saved spinel frames. + * @param[in] aContext The context to call the handler. + * + */ + void SetFrameHandler(ReceivedFrameHandler aReceivedFrameHandler, + SavedFrameHandler aSavedFrameHandler, + void *aContext); + + /* + * Returns the spinel interface. + * + * @returns A pointer to the spinel interface object. + * + */ + SpinelInterface *GetSpinelInterface(void) const { return mSpinelInterface; } + + /** + * Returns if the co-processor has some capability + * + * @param[in] aCapability The capability queried. + * + * @returns `true` if the co-processor has the capability. `false` otherwise. + * + */ + bool CoprocessorHasCap(unsigned int aCapability) { return mCoprocessorCaps.Contains(aCapability); } + +private: + static constexpr uint16_t kMaxSpinelFrame = SPINEL_FRAME_MAX_SIZE; + static constexpr uint16_t kVersionStringSize = 128; + static constexpr uint32_t kUsPerMs = 1000; ///< Microseconds per millisecond. + static constexpr uint32_t kMaxWaitTime = 2000; ///< Max time to wait for response in milliseconds. + static constexpr uint16_t kCapsBufferSize = 100; ///< Max buffer size used to store `SPINEL_PROP_CAPS` value. + + /** + * Represents an array of elements with a fixed max size. + * + * @tparam Type The array element type. + * @tparam kMaxSize Specifies the max array size (maximum number of elements in the array). + * + */ + template class Array + { + static_assert(kMaxSize != 0, "Array `kMaxSize` cannot be zero"); + + public: + Array(void) + : mLength(0) + { + } + + uint16_t GetMaxSize(void) const { return kMaxSize; } + + bool IsFull(void) const { return (mLength == GetMaxSize()); } + + otError PushBack(const Type &aEntry) + { + return IsFull() ? OT_ERROR_NO_BUFS : (mElements[mLength++] = aEntry, OT_ERROR_NONE); + } + + const Type *Find(const Type &aEntry) const + { + const Type *matched = nullptr; + + for (const Type &element : *this) + { + if (element == aEntry) + { + matched = &element; + break; + } + } + + return matched; + } + + bool Contains(const Type &aEntry) const { return Find(aEntry) != nullptr; } + + Type *begin(void) { return &mElements[0]; } + Type *end(void) { return &mElements[mLength]; } + const Type *begin(void) const { return &mElements[0]; } + const Type *end(void) const { return &mElements[mLength]; } + + private: + Type mElements[kMaxSize]; + uint16_t mLength; + }; + + otError WaitResponse(void); + + static void HandleReceivedFrame(void *aContext); + void HandleReceivedFrame(void); + + static void HandleInitialFrame(const uint8_t *aFrame, + uint16_t aLength, + uint8_t aHeader, + bool &aSave, + void *aContext); + void HandleInitialFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aSave); + + otError SendCommand(uint32_t aCommand, spinel_prop_key_t aKey, spinel_tid_t aTid); + + otError CheckSpinelVersion(void); + otError GetCoprocessorVersion(void); + otError GetCoprocessorCaps(void); + + void ProcessFrameQueue(void); + + SpinelInterface::RxFrameBuffer mRxFrameBuffer; + SpinelInterface *mSpinelInterface; + + spinel_prop_key_t mWaitingKey; ///< The property key of current transaction. + bool mIsWaitingForResponse; + + spinel_iid_t mIid; + Array mIidList; + + ReceivedFrameHandler mReceivedFrameHandler; + SavedFrameHandler mSavedFrameHandler; + void *mFrameHandlerContext; + + int mSpinelVersionMajor; + int mSpinelVersionMinor; + + bool mIsCoprocessorReady; + char mVersion[kVersionStringSize]; + + Array mCoprocessorCaps; +}; + +} // namespace Spinel +} // namespace ot + +#endif // SPINEL_DRIVER_HPP_