diff --git a/.github/workflows/simulation-1.2.yml b/.github/workflows/simulation-1.2.yml index bca95fd0f..b9054e00e 100644 --- a/.github/workflows/simulation-1.2.yml +++ b/.github/workflows/simulation-1.2.yml @@ -220,7 +220,7 @@ jobs: - uses: actions/checkout@v2 - name: Bootstrap run: | - sudo apt-get --no-install-recommends install -y expect ninja-build lcov + sudo apt-get --no-install-recommends install -y expect ninja-build lcov socat - name: Run RCP Mode run: | OT_OPTIONS=-DOT_READLINE=OFF OT_NODE_TYPE=rcp ./script/test build expect diff --git a/.github/workflows/simulation.yml b/.github/workflows/simulation.yml index 831a9181e..46d86e2c2 100644 --- a/.github/workflows/simulation.yml +++ b/.github/workflows/simulation.yml @@ -279,7 +279,7 @@ jobs: - uses: actions/checkout@v2 - name: Bootstrap run: | - OT_OPTIONS=-DOT_READLINE=OFF sudo apt-get --no-install-recommends install -y expect ninja-build lcov + OT_OPTIONS=-DOT_READLINE=OFF sudo apt-get --no-install-recommends install -y expect ninja-build lcov socat - name: Run run: | OT_OPTIONS=-DOT_TIME_SYNC=ON VIRTUAL_TIME=0 ./script/test build expect diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 741aaa980..7d552f593 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -297,6 +297,13 @@ if(OT_OTNS) target_compile_definitions(ot-config INTERFACE "OPENTHREAD_CONFIG_OTNS_ENABLE=1") endif() +set(OT_RCP_RESTORATION_MAX_COUNT "0" CACHE STRING "set max RCP restoration count") +if(OT_RCP_RESTORATION_MAX_COUNT MATCHES "^[0-9]+$") + target_compile_definitions(ot-config INTERFACE "OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT=${OT_RCP_RESTORATION_MAX_COUNT}") +else() + message(FATAL_ERROR "Invalid max RCP restoration count: ${OT_RCP_RESTORATION_MAX_COUNT}") +endif() + # Checks if(OT_PLATFORM_UDP AND OT_UDP_FORWARD) message(FATAL_ERROR "OT_PLATFORM_UDP and OT_UDP_FORWARD are exclusive") diff --git a/examples/common-switches.mk b/examples/common-switches.mk index d09b05604..b1cc708d0 100644 --- a/examples/common-switches.mk +++ b/examples/common-switches.mk @@ -28,56 +28,57 @@ # OpenThread Features (Makefile default configuration). -BACKBONE_ROUTER ?= 0 -BIG_ENDIAN ?= 0 -BORDER_AGENT ?= 0 -BORDER_ROUTER ?= 0 -COAP ?= 0 -COAP_OBSERVE ?= 0 -COAPS ?= 0 -COMMISSIONER ?= 0 -COVERAGE ?= 0 -CHANNEL_MANAGER ?= 0 -CHANNEL_MONITOR ?= 0 -CHILD_SUPERVISION ?= 0 -CLI_TRANSPORT ?= UART -DATASET_UPDATER ?= 0 -DEBUG ?= 0 -DHCP6_CLIENT ?= 0 -DHCP6_SERVER ?= 0 -DIAGNOSTIC ?= 0 -DISABLE_DOC ?= 0 -DISABLE_TOOLS ?= 0 -DNS_CLIENT ?= 0 -DUA ?= 0 -DYNAMIC_LOG_LEVEL ?= 0 -ECDSA ?= 0 -EXTERNAL_HEAP ?= 0 -IP6_FRAGM ?= 0 -JAM_DETECTION ?= 0 -JOINER ?= 0 -LEGACY ?= 0 +BACKBONE_ROUTER ?= 0 +BIG_ENDIAN ?= 0 +BORDER_AGENT ?= 0 +BORDER_ROUTER ?= 0 +COAP ?= 0 +COAP_OBSERVE ?= 0 +COAPS ?= 0 +COMMISSIONER ?= 0 +COVERAGE ?= 0 +CHANNEL_MANAGER ?= 0 +CHANNEL_MONITOR ?= 0 +CHILD_SUPERVISION ?= 0 +CLI_TRANSPORT ?= UART +DATASET_UPDATER ?= 0 +DEBUG ?= 0 +DHCP6_CLIENT ?= 0 +DHCP6_SERVER ?= 0 +DIAGNOSTIC ?= 0 +DISABLE_DOC ?= 0 +DISABLE_TOOLS ?= 0 +DNS_CLIENT ?= 0 +DUA ?= 0 +DYNAMIC_LOG_LEVEL ?= 0 +ECDSA ?= 0 +EXTERNAL_HEAP ?= 0 +IP6_FRAGM ?= 0 +JAM_DETECTION ?= 0 +JOINER ?= 0 +LEGACY ?= 0 ifeq ($(REFERENCE_DEVICE),1) -LOG_OUTPUT ?= APP +LOG_OUTPUT ?= APP endif -LINK_RAW ?= 0 -MAC_FILTER ?= 0 -MESSAGE_USE_HEAP ?= 0 -MLE_LONG_ROUTES ?= 0 -MLR ?= 0 -MTD_NETDIAG ?= 0 -MULTIPLE_INSTANCE ?= 0 -OTNS ?= 0 -PLATFORM_UDP ?= 0 -REFERENCE_DEVICE ?= 0 -SERVICE ?= 0 -SETTINGS_RAM ?= 0 +LINK_RAW ?= 0 +MAC_FILTER ?= 0 +MESSAGE_USE_HEAP ?= 0 +MLE_LONG_ROUTES ?= 0 +MLR ?= 0 +MTD_NETDIAG ?= 0 +MULTIPLE_INSTANCE ?= 0 +OTNS ?= 0 +PLATFORM_UDP ?= 0 +REFERENCE_DEVICE ?= 0 +SERVICE ?= 0 +SETTINGS_RAM ?= 0 # SLAAC is enabled by default -SLAAC ?= 1 -SNTP_CLIENT ?= 0 -THREAD_VERSION ?= 1.1 -TIME_SYNC ?= 0 -UDP_FORWARD ?= 0 +SLAAC ?= 1 +SNTP_CLIENT ?= 0 +THREAD_VERSION ?= 1.1 +TIME_SYNC ?= 0 +UDP_FORWARD ?= 0 +RCP_RESTORATION_MAX_COUNT ?= 0 ifeq ($(BACKBONE_ROUTER),1) @@ -310,6 +311,8 @@ ifneq ($(SPINEL_ENCRYPTER_LIBS),) configure_OPTIONS += --with-ncp-spinel-encrypter-libs=$(SPINEL_ENCRYPTER_LIBS) endif +COMMONCFLAGS += -DOPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT=${RCP_RESTORATION_MAX_COUNT} + ifeq ($(FULL_LOGS),1) # HINT: Add more here, or comment out ones you do not need/want LOG_FLAGS += -DOPENTHREAD_CONFIG_LOG_LEVEL=OT_LOG_LEVEL_DEBG diff --git a/script/check-posix-build-autotools b/script/check-posix-build-autotools index a8cc3d248..17cf4ad11 100755 --- a/script/check-posix-build-autotools +++ b/script/check-posix-build-autotools @@ -37,11 +37,11 @@ reset_source() build() { reset_source - make -f src/posix/Makefile-posix + make -f src/posix/Makefile-posix RCP_RESTORATION_MAX_COUNT=2 if [[ $OSTYPE != "darwin"* ]]; then reset_source - make -f src/posix/Makefile-posix RCP_BUS=spi + make -f src/posix/Makefile-posix RCP_RESTORATION_MAX_COUNT=2 RCP_BUS=spi fi } diff --git a/script/cmake-build b/script/cmake-build index 6906282cb..e8f3d942a 100755 --- a/script/cmake-build +++ b/script/cmake-build @@ -93,6 +93,7 @@ readonly OT_POSIX_SIM_COMMON_OPTIONS=( "-DOT_COVERAGE=ON" "-DOT_LOG_LEVEL_DYNAMIC=ON" "-DOT_COMPILE_WARNING_AS_ERROR=ON" + "-DOT_RCP_RESTORATION_MAX_COUNT=2" ) die() diff --git a/script/test b/script/test index 196c03f93..de63f24ba 100755 --- a/script/test +++ b/script/test @@ -449,6 +449,8 @@ do_upload_codecov() envsetup() { + export THREAD_VERSION + if [[ ${OT_NODE_TYPE} == rcp* ]]; then export RADIO_DEVICE="${OT_BUILDDIR}/cmake/openthread-simulation-${THREAD_VERSION}/examples/apps/ncp/ot-rcp" export OT_CLI_PATH="${OT_BUILDDIR}/cmake/openthread-posix-${THREAD_VERSION}/src/posix/ot-cli" diff --git a/src/lib/hdlc/hdlc.cpp b/src/lib/hdlc/hdlc.cpp index 11c9fe956..b75b42e47 100644 --- a/src/lib/hdlc/hdlc.cpp +++ b/src/lib/hdlc/hdlc.cpp @@ -209,6 +209,13 @@ Decoder::Decoder(FrameWritePointer &aFrameWritePointer, FrameHandler aFrameHandl { } +void Decoder::Reset(void) +{ + mState = kStateNoSync; + mFcs = 0; + mDecodedLength = 0; +} + void Decoder::Decode(const uint8_t *aData, uint16_t aLength) { while (aLength--) diff --git a/src/lib/hdlc/hdlc.hpp b/src/lib/hdlc/hdlc.hpp index 4092f4346..ea97ce748 100644 --- a/src/lib/hdlc/hdlc.hpp +++ b/src/lib/hdlc/hdlc.hpp @@ -566,6 +566,12 @@ public: */ void Decode(const uint8_t *aData, uint16_t aLength); + /** + * This method resets internal states of the decoder. + * + */ + void Reset(void); + private: enum State { diff --git a/src/lib/spinel/openthread-spinel-config.h b/src/lib/spinel/openthread-spinel-config.h index 390e3e30b..3cd91f243 100644 --- a/src/lib/spinel/openthread-spinel-config.h +++ b/src/lib/spinel/openthread-spinel-config.h @@ -44,4 +44,15 @@ #define OPENTHREAD_SPINEL_CONFIG_OPENTHREAD_MESSAGE_ENABLE 0 #endif +/** + * @def OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT + * + * Defines the max count of RCP failures allowed to be recovered. + * 0 means to disable RCP failure recovering. + * + */ +#ifndef OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT +#define OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT 0 +#endif + #endif // OPENTHREAD_SPINEL_CONFIG_H_ diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp index 8a7ff784a..4ed17448c 100644 --- a/src/lib/spinel/radio_spinel.hpp +++ b/src/lib/spinel/radio_spinel.hpp @@ -36,6 +36,7 @@ #include +#include "openthread-spinel-config.h" #include "spinel.h" #include "spinel_interface.hpp" #include "ncp/ncp_config.h" @@ -819,6 +820,21 @@ private: otError RequestV(bool aWait, uint32_t aCommand, spinel_prop_key_t aKey, const char *aFormat, va_list aArgs); otError Request(bool aWait, uint32_t aCommand, spinel_prop_key_t aKey, const char *aFormat, ...); + otError RequestWithPropertyFormat(const char * aPropertyFormat, + uint32_t aCommand, + spinel_prop_key_t aKey, + const char * aFormat, + ...); + otError RequestWithPropertyFormatV(const char * aPropertyFormat, + uint32_t aCommand, + spinel_prop_key_t aKey, + const char * aFormat, + va_list aArgs); + otError RequestWithExpectedCommandV(uint32_t aExpectedCommand, + uint32_t aCommand, + spinel_prop_key_t aKey, + const char * aFormat, + va_list aArgs); otError WaitResponse(void); otError SendReset(void); otError SendCommand(uint32_t command, @@ -859,6 +875,14 @@ private: void CalcRcpTimeOffset(void); + void HandleRcpUnexpectedReset(spinel_status_t aStatus); + void HandleRcpTimeout(void); + void RecoverFromRcpFailure(void); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + void RestoreProperties(void); +#endif + otInstance *mInstance; SpinelInterface::RxFrameBuffer mRxFrameBuffer; @@ -899,6 +923,38 @@ private: 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_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + + bool mResetRadioOnStartup : 1; ///< Whether should send reset command when init. + int16_t mRcpFailureCount; ///< Count of consecutive RCP failures. + + // Properties set by core. + uint8_t mKeyIdMode; + uint8_t mKeyId; + otMacKey mPrevKey; + otMacKey mCurrKey; + otMacKey mNextKey; + uint16_t mSrcMatchShortEntries[OPENTHREAD_CONFIG_MLE_MAX_CHILDREN]; + int16_t mSrcMatchShortEntryCount; + otExtAddress mSrcMatchExtEntries[OPENTHREAD_CONFIG_MLE_MAX_CHILDREN]; + int16_t mSrcMatchExtEntryCount; + uint8_t mScanChannel; + uint16_t mScanDuration; + int8_t mCcaEnergyDetectThreshold; + int8_t mTransmitPower; + int8_t mFemLnaGain; + bool mCoexEnabled : 1; + + bool mMacKeySet : 1; ///< Whether MAC key has been set. + bool mCcaEnergyDetectThresholdSet : 1; ///< Whether CCA energy detect threshold has been set. + bool mTransmitPowerSet : 1; ///< Whether transmit power has been set. + bool mCoexEnabledSet : 1; ///< Whether coex enabled has been set. + bool mFemLnaGainSet : 1; ///< Whether FEM LNA gain has benn set. + bool mRcpFailed : 1; ///< RCP failure happened, should recover and retry operation. + bool mEnergyScanning : 1; ///< If fails while scanning, restarts scanning. + +#endif // OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + #if OPENTHREAD_CONFIG_DIAG_ENABLE bool mDiagMode; char * mDiagOutput; diff --git a/src/lib/spinel/radio_spinel_impl.hpp b/src/lib/spinel/radio_spinel_impl.hpp index 5339f0288..d0bbc3d3f 100644 --- a/src/lib/spinel/radio_spinel_impl.hpp +++ b/src/lib/spinel/radio_spinel_impl.hpp @@ -43,10 +43,12 @@ #include "common/code_utils.hpp" #include "common/encoding.hpp" +#include "common/instance.hpp" #include "common/logging.hpp" #include "common/new.hpp" #include "common/settings.hpp" #include "lib/platform/exit_code.h" +#include "lib/spinel/radio_spinel.hpp" #include "lib/spinel/spinel_decoder.hpp" #include "meshcop/dataset.hpp" #include "meshcop/meshcop_tlvs.hpp" @@ -192,6 +194,18 @@ RadioSpinel::RadioSpinel(void) , mIsReady(false) , mSupportsLogStream(false) , mIsTimeSynced(false) +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + , mRcpFailureCount(0) + , mSrcMatchShortEntryCount(0) + , mSrcMatchExtEntryCount(0) + , mMacKeySet(false) + , mCcaEnergyDetectThresholdSet(false) + , mTransmitPowerSet(false) + , mCoexEnabledSet(false) + , mFemLnaGainSet(false) + , mRcpFailed(false) + , mEnergyScanning(false) +#endif #if OPENTHREAD_CONFIG_DIAG_ENABLE , mDiagMode(false) , mDiagOutput(nullptr) @@ -209,6 +223,10 @@ void RadioSpinel::Init(bool aResetRadio, bool { otError error = OT_ERROR_NONE; +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mResetRadioOnStartup = aResetRadio; +#endif + if (aResetRadio) { SuccessOrExit(error = SendReset()); @@ -764,9 +782,8 @@ void RadioSpinel::HandleValueIs(spinel_prop_k { if (IsEnabled()) { - // If RCP crashes/resets while radio was enabled, posix app exits. - otLogCritPlat("Unexpected RCP reset: %s", spinel_status_to_cstr(status)); - DieNow(OT_EXIT_RADIO_SPINEL_RESET); + HandleRcpUnexpectedReset(status); + ExitNow(); } otLogInfoPlat("RCP reset: %s", spinel_status_to_cstr(status)); @@ -785,6 +802,11 @@ void RadioSpinel::HandleValueIs(spinel_prop_k unpacked = spinel_datatype_unpack(aBuffer, aLength, "Cc", &scanChannel, &maxRssi); VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mEnergyScanning = false; +#endif + otPlatRadioEnergyScanDone(mInstance, maxRssi); } else if (aKey == SPINEL_PROP_STREAM_DEBUG) @@ -992,7 +1014,8 @@ void RadioSpinel::ProcessRadioStateMachine(vo else if (mState == kStateTransmitting && otPlatTimeGet() >= mTxRadioEndUs) { // Frame has been successfully passed to radio, but no `TransmitDone` event received within TX_WAIT_US. - DieNowWithMessage("radio tx timeout", OT_EXIT_FAILURE); + otLogWarnPlat("radio tx timeout"); + HandleRcpTimeout(); } } @@ -1002,16 +1025,20 @@ void RadioSpinel::Process(const ProcessContex if (mRxFrameBuffer.HasSavedFrame()) { ProcessFrameQueue(); + RecoverFromRcpFailure(); } GetSpinelInterface().Process(aContext); + RecoverFromRcpFailure(); if (mRxFrameBuffer.HasSavedFrame()) { ProcessFrameQueue(); + RecoverFromRcpFailure(); } ProcessRadioStateMachine(); + RecoverFromRcpFailure(); CalcRcpTimeOffset(); } @@ -1056,6 +1083,15 @@ otError RadioSpinel::SetMacKey(uint8_t aKeyIdMode, aKeyId, aPrevKey.m8, sizeof(otMacKey), aCurrKey.m8, sizeof(otMacKey), aNextKey.m8, sizeof(otMacKey))); +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mKeyIdMode = aKeyIdMode; + mKeyId = aKeyId; + memcpy(mPrevKey.m8, aPrevKey.m8, OT_MAC_KEY_SIZE); + memcpy(mCurrKey.m8, aCurrKey.m8, OT_MAC_KEY_SIZE); + memcpy(mNextKey.m8, aNextKey.m8, OT_MAC_KEY_SIZE); + mMacKeySet = true; +#endif + exit: return error; } @@ -1113,37 +1149,129 @@ otError RadioSpinel::EnableSrcMatch(bool aEna template otError RadioSpinel::AddSrcMatchShortEntry(uint16_t aShortAddress) { - return Insert(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, SPINEL_DATATYPE_UINT16_S, aShortAddress); + otError error; + + SuccessOrExit(error = Insert(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, SPINEL_DATATYPE_UINT16_S, aShortAddress)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + assert(mSrcMatchShortEntryCount < OPENTHREAD_CONFIG_MLE_MAX_CHILDREN); + + for (int i = 0; i < mSrcMatchShortEntryCount; ++i) + { + if (mSrcMatchShortEntries[i] == aShortAddress) + { + ExitNow(); + } + } + mSrcMatchShortEntries[mSrcMatchShortEntryCount] = aShortAddress; + ++mSrcMatchShortEntryCount; +#endif + +exit: + return error; } template otError RadioSpinel::AddSrcMatchExtEntry(const otExtAddress &aExtAddress) { - return Insert(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, SPINEL_DATATYPE_EUI64_S, aExtAddress.m8); + otError error; + + SuccessOrExit(error = + Insert(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, SPINEL_DATATYPE_EUI64_S, aExtAddress.m8)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + assert(mSrcMatchExtEntryCount < OPENTHREAD_CONFIG_MLE_MAX_CHILDREN); + + for (int i = 0; i < mSrcMatchExtEntryCount; ++i) + { + if (memcmp(aExtAddress.m8, mSrcMatchExtEntries[i].m8, OT_EXT_ADDRESS_SIZE) == 0) + { + ExitNow(); + } + } + mSrcMatchExtEntries[mSrcMatchExtEntryCount] = aExtAddress; + ++mSrcMatchExtEntryCount; +#endif + +exit: + return error; } template otError RadioSpinel::ClearSrcMatchShortEntry(uint16_t aShortAddress) { - return Remove(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, SPINEL_DATATYPE_UINT16_S, aShortAddress); + otError error; + + SuccessOrExit(error = Remove(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, SPINEL_DATATYPE_UINT16_S, aShortAddress)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + for (int i = 0; i < mSrcMatchShortEntryCount; ++i) + { + if (mSrcMatchShortEntries[i] == aShortAddress) + { + mSrcMatchShortEntries[i] = mSrcMatchShortEntries[mSrcMatchShortEntryCount - 1]; + --mSrcMatchShortEntryCount; + break; + } + } +#endif + +exit: + return error; } template otError RadioSpinel::ClearSrcMatchExtEntry(const otExtAddress &aExtAddress) { - return Remove(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, SPINEL_DATATYPE_EUI64_S, aExtAddress.m8); + otError error; + + SuccessOrExit(error = + Remove(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, SPINEL_DATATYPE_EUI64_S, aExtAddress.m8)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + for (int i = 0; i < mSrcMatchExtEntryCount; ++i) + { + if (memcmp(mSrcMatchExtEntries[i].m8, aExtAddress.m8, OT_EXT_ADDRESS_SIZE) == 0) + { + mSrcMatchExtEntries[i] = mSrcMatchExtEntries[mSrcMatchExtEntryCount - 1]; + --mSrcMatchExtEntryCount; + break; + } + } +#endif + +exit: + return error; } template otError RadioSpinel::ClearSrcMatchShortEntries(void) { - return Set(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, nullptr); + otError error; + + SuccessOrExit(error = Set(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, nullptr)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mSrcMatchShortEntryCount = 0; +#endif + +exit: + return error; } template otError RadioSpinel::ClearSrcMatchExtEntries(void) { - return Set(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, nullptr); + otError error; + + SuccessOrExit(error = Set(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, nullptr)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mSrcMatchExtEntryCount = 0; +#endif + +exit: + return error; } template @@ -1187,7 +1315,17 @@ int8_t RadioSpinel::GetRssi(void) template otError RadioSpinel::SetCoexEnabled(bool aEnabled) { - return Set(SPINEL_PROP_RADIO_COEX_ENABLE, SPINEL_DATATYPE_BOOL_S, aEnabled); + otError error; + + SuccessOrExit(error = Set(SPINEL_PROP_RADIO_COEX_ENABLE, SPINEL_DATATYPE_BOOL_S, aEnabled)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mCoexEnabled = aEnabled; + mCoexEnabledSet = true; +#endif + +exit: + return error; } template @@ -1244,7 +1382,16 @@ otError RadioSpinel::GetCoexMetrics(otRadioCo template otError RadioSpinel::SetTransmitPower(int8_t aPower) { - otError error = Set(SPINEL_PROP_PHY_TX_POWER, SPINEL_DATATYPE_INT8_S, aPower); + otError error; + + SuccessOrExit(error = Set(SPINEL_PROP_PHY_TX_POWER, SPINEL_DATATYPE_INT8_S, aPower)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mTransmitPower = aPower; + mTransmitPowerSet = true; +#endif + +exit: LogIfFail("Set transmit power failed", error); return error; } @@ -1252,7 +1399,16 @@ otError RadioSpinel::SetTransmitPower(int8_t template otError RadioSpinel::SetCcaEnergyDetectThreshold(int8_t aThreshold) { - otError error = Set(SPINEL_PROP_PHY_CCA_THRESHOLD, SPINEL_DATATYPE_INT8_S, aThreshold); + otError error; + + SuccessOrExit(error = Set(SPINEL_PROP_PHY_CCA_THRESHOLD, SPINEL_DATATYPE_INT8_S, aThreshold)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mCcaEnergyDetectThreshold = aThreshold; + mCcaEnergyDetectThresholdSet = true; +#endif + +exit: LogIfFail("Set CCA ED threshold failed", error); return error; } @@ -1260,7 +1416,16 @@ otError RadioSpinel::SetCcaEnergyDetectThresh template otError RadioSpinel::SetFemLnaGain(int8_t aGain) { - otError error = Set(SPINEL_PROP_PHY_FEM_LNA_GAIN, SPINEL_DATATYPE_INT8_S, aGain); + otError error; + + SuccessOrExit(error = Set(SPINEL_PROP_PHY_FEM_LNA_GAIN, SPINEL_DATATYPE_INT8_S, aGain)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mFemLnaGain = aGain; + mFemLnaGainSet = true; +#endif + +exit: LogIfFail("Set FEM LNA gain failed", error); return error; } @@ -1272,6 +1437,12 @@ otError RadioSpinel::EnergyScan(uint8_t aScan VerifyOrExit(mRadioCaps & OT_RADIO_CAPS_ENERGY_SCAN, error = OT_ERROR_NOT_CAPABLE); +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mScanChannel = aScanChannel; + mScanDuration = aScanDuration; + mEnergyScanning = true; +#endif + SuccessOrExit(error = Set(SPINEL_PROP_MAC_SCAN_MASK, SPINEL_DATATYPE_DATA_S, &aScanChannel, sizeof(uint8_t))); SuccessOrExit(error = Set(SPINEL_PROP_MAC_SCAN_PERIOD, SPINEL_DATATYPE_UINT16_S, aScanDuration)); SuccessOrExit(error = Set(SPINEL_PROP_MAC_SCAN_STATE, SPINEL_DATATYPE_UINT8_S, SPINEL_SCAN_STATE_ENERGY)); @@ -1287,11 +1458,17 @@ otError RadioSpinel::Get(spinel_prop_key_t aK assert(mWaitingTid == 0); - mPropertyFormat = aFormat; - va_start(mPropertyArgs, aFormat); - error = RequestV(true, SPINEL_CMD_PROP_VALUE_GET, aKey, nullptr, mPropertyArgs); - va_end(mPropertyArgs); - mPropertyFormat = nullptr; +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + do + { + RecoverFromRcpFailure(); +#endif + va_start(mPropertyArgs, aFormat); + error = RequestWithPropertyFormatV(aFormat, SPINEL_CMD_PROP_VALUE_GET, aKey, nullptr, mPropertyArgs); + va_end(mPropertyArgs); +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + } while (mRcpFailed); +#endif return error; } @@ -1308,11 +1485,18 @@ otError RadioSpinel::GetWithParam(spinel_prop 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 = nullptr; +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + do + { + RecoverFromRcpFailure(); +#endif + va_start(mPropertyArgs, aFormat); + error = RequestWithPropertyFormat(aFormat, SPINEL_CMD_PROP_VALUE_GET, aKey, SPINEL_DATATYPE_DATA_S, aParam, + aParamSize); + va_end(mPropertyArgs); +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + } while (mRcpFailed); +#endif return error; } @@ -1324,11 +1508,18 @@ otError RadioSpinel::Set(spinel_prop_key_t aK assert(mWaitingTid == 0); - mExpectedCommand = SPINEL_CMD_PROP_VALUE_IS; - va_start(mPropertyArgs, aFormat); - error = RequestV(true, SPINEL_CMD_PROP_VALUE_SET, aKey, aFormat, mPropertyArgs); - va_end(mPropertyArgs); - mExpectedCommand = SPINEL_CMD_NOOP; +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + do + { + RecoverFromRcpFailure(); +#endif + va_start(mPropertyArgs, aFormat); + error = RequestWithExpectedCommandV(SPINEL_CMD_PROP_VALUE_IS, SPINEL_CMD_PROP_VALUE_SET, aKey, aFormat, + mPropertyArgs); + va_end(mPropertyArgs); +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + } while (mRcpFailed); +#endif return error; } @@ -1340,11 +1531,18 @@ otError RadioSpinel::Insert(spinel_prop_key_t assert(mWaitingTid == 0); - mExpectedCommand = SPINEL_CMD_PROP_VALUE_INSERTED; - va_start(mPropertyArgs, aFormat); - error = RequestV(true, SPINEL_CMD_PROP_VALUE_INSERT, aKey, aFormat, mPropertyArgs); - va_end(mPropertyArgs); - mExpectedCommand = SPINEL_CMD_NOOP; +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + do + { + RecoverFromRcpFailure(); +#endif + va_start(mPropertyArgs, aFormat); + error = RequestWithExpectedCommandV(SPINEL_CMD_PROP_VALUE_INSERTED, SPINEL_CMD_PROP_VALUE_INSERT, aKey, aFormat, + mPropertyArgs); + va_end(mPropertyArgs); +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + } while (mRcpFailed); +#endif return error; } @@ -1356,11 +1554,18 @@ otError RadioSpinel::Remove(spinel_prop_key_t assert(mWaitingTid == 0); - mExpectedCommand = SPINEL_CMD_PROP_VALUE_REMOVED; - va_start(mPropertyArgs, aFormat); - error = RequestV(true, SPINEL_CMD_PROP_VALUE_REMOVE, aKey, aFormat, mPropertyArgs); - va_end(mPropertyArgs); - mExpectedCommand = SPINEL_CMD_NOOP; +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + do + { + RecoverFromRcpFailure(); +#endif + va_start(mPropertyArgs, aFormat); + error = RequestWithExpectedCommandV(SPINEL_CMD_PROP_VALUE_REMOVED, SPINEL_CMD_PROP_VALUE_REMOVE, aKey, aFormat, + mPropertyArgs); + va_end(mPropertyArgs); +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + } while (mRcpFailed); +#endif return error; } @@ -1378,15 +1583,25 @@ otError RadioSpinel::WaitResponse(void) uint64_t remain; now = otPlatTimeGet(); - VerifyOrDie(end > now, OT_EXIT_RADIO_SPINEL_NO_RESPONSE); + if (end <= now) + { + HandleRcpTimeout(); + ExitNow(mError = OT_ERROR_NONE); + } remain = end - now; - VerifyOrDie(mSpinelInterface.WaitForFrame(remain) == OT_ERROR_NONE, OT_EXIT_RADIO_SPINEL_NO_RESPONSE); + if (mSpinelInterface.WaitForFrame(remain) != OT_ERROR_NONE) + { + HandleRcpTimeout(); + ExitNow(mError = OT_ERROR_NONE); + } } while (mWaitingTid || !mIsReady); LogIfFail("Error waiting response", mError); // This indicates end of waiting response. mWaitingKey = SPINEL_PROP_LAST_STATUS; + +exit: return mError; } @@ -1506,6 +1721,55 @@ otError RadioSpinel::Request(bool return status; } +template +otError RadioSpinel::RequestWithPropertyFormat(const char * aPropertyFormat, + uint32_t aCommand, + spinel_prop_key_t aKey, + const char * aFormat, + ...) +{ + otError error; + va_list args; + + va_start(args, aFormat); + error = RequestWithPropertyFormatV(aPropertyFormat, aCommand, aKey, aFormat, args); + va_end(args); + + return error; +} + +template +otError RadioSpinel::RequestWithPropertyFormatV(const char * aPropertyFormat, + uint32_t aCommand, + spinel_prop_key_t aKey, + const char * aFormat, + va_list aArgs) +{ + otError error; + + mPropertyFormat = aPropertyFormat; + error = RequestV(true, aCommand, aKey, aFormat, aArgs); + mPropertyFormat = nullptr; + + return error; +} + +template +otError RadioSpinel::RequestWithExpectedCommandV(uint32_t aExpectedCommand, + uint32_t aCommand, + spinel_prop_key_t aKey, + const char * aFormat, + va_list aArgs) +{ + otError error; + + mExpectedCommand = aExpectedCommand; + error = RequestV(true, aCommand, aKey, aFormat, aArgs); + mExpectedCommand = SPINEL_CMD_NOOP; + + return error; +} + template void RadioSpinel::HandleTransmitDone(uint32_t aCommand, spinel_prop_key_t aKey, @@ -1835,5 +2099,162 @@ uint32_t RadioSpinel::GetBusSpeed(void) const return mSpinelInterface.GetBusSpeed(); } +template +void RadioSpinel::HandleRcpUnexpectedReset(spinel_status_t aStatus) +{ + otLogCritPlat("Unexpected RCP reset: %s", spinel_status_to_cstr(aStatus)); + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mRcpFailed = true; +#else + DieNow(OT_EXIT_RADIO_SPINEL_RESET); +#endif +} + +template +void RadioSpinel::HandleRcpTimeout(void) +{ +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + mRcpFailed = true; +#else + DieNow(OT_EXIT_RADIO_SPINEL_NO_RESPONSE); +#endif +} + +template +void RadioSpinel::RecoverFromRcpFailure(void) +{ +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + constexpr int16_t kMaxFailureCount = OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT; + State recoveringState = mState; + + if (!mRcpFailed) + { + ExitNow(); + } + mRcpFailed = false; + + otLogWarnPlat("RCP failure detected"); + + ++mRcpFailureCount; + if (mRcpFailureCount > kMaxFailureCount) + { + otLogCritPlat("Too many rcp failures, exiting"); + DieNow(OT_EXIT_FAILURE); + } + + otLogWarnPlat("Trying to recover (%d/%d)", mRcpFailureCount, kMaxFailureCount); + + mState = kStateDisabled; + mRxFrameBuffer.Clear(); + mSpinelInterface.OnRcpReset(); + mCmdTidsInUse = 0; + mCmdNextTid = 1; + mTxRadioTid = 0; + mWaitingTid = 0; + mWaitingKey = SPINEL_PROP_LAST_STATUS; + mIsReady = false; + mIsTimeSynced = false; + + if (mResetRadioOnStartup) + { + SuccessOrDie(SendReset()); + } + + SuccessOrDie(WaitResponse()); + + SuccessOrDie(Set(SPINEL_PROP_PHY_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); + mState = kStateSleep; + + RestoreProperties(); + + switch (recoveringState) + { + case kStateDisabled: + case kStateSleep: + break; + case kStateReceive: + SuccessOrDie(Set(SPINEL_PROP_MAC_RAW_STREAM_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); + mState = kStateReceive; + break; + case kStateTransmitting: + case kStateTransmitDone: + SuccessOrDie(Set(SPINEL_PROP_MAC_RAW_STREAM_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); + mTxError = OT_ERROR_ABORT; + mState = kStateTransmitDone; + break; + } + + if (mEnergyScanning) + { + SuccessOrDie(EnergyScan(mScanChannel, mScanDuration)); + } + + --mRcpFailureCount; + +exit: + return; +#endif // OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 +} + +#if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 +template +void RadioSpinel::RestoreProperties(void) +{ + Settings::NetworkInfo networkInfo; + + SuccessOrDie(Set(SPINEL_PROP_MAC_15_4_PANID, SPINEL_DATATYPE_UINT16_S, mPanId)); + SuccessOrDie(Set(SPINEL_PROP_MAC_15_4_SADDR, SPINEL_DATATYPE_UINT16_S, mShortAddress)); + SuccessOrDie(Set(SPINEL_PROP_MAC_15_4_LADDR, SPINEL_DATATYPE_EUI64_S, mExtendedAddress.m8)); + SuccessOrDie(Set(SPINEL_PROP_PHY_CHAN, SPINEL_DATATYPE_UINT8_S, mChannel)); + + if (mMacKeySet) + { + SuccessOrDie(Set(SPINEL_PROP_RCP_MAC_KEY, + SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_DATA_WLEN_S + SPINEL_DATATYPE_DATA_WLEN_S SPINEL_DATATYPE_DATA_WLEN_S, + mKeyIdMode, mKeyId, mPrevKey.m8, sizeof(otMacKey), mCurrKey.m8, sizeof(otMacKey), mNextKey.m8, + sizeof(otMacKey))); + } + + SuccessOrDie(Instance::Get().template Get().ReadNetworkInfo(networkInfo)); + SuccessOrDie(Set(SPINEL_PROP_RCP_MAC_FRAME_COUNTER, SPINEL_DATATYPE_UINT32_S, networkInfo.GetMacFrameCounter())); + + for (int i = 0; i < mSrcMatchShortEntryCount; ++i) + { + SuccessOrDie( + Insert(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, SPINEL_DATATYPE_UINT16_S, mSrcMatchShortEntries[i])); + } + + for (int i = 0; i < mSrcMatchExtEntryCount; ++i) + { + SuccessOrDie( + Insert(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, SPINEL_DATATYPE_EUI64_S, mSrcMatchExtEntries[i].m8)); + } + + if (mCcaEnergyDetectThresholdSet) + { + SuccessOrDie(Set(SPINEL_PROP_PHY_CCA_THRESHOLD, SPINEL_DATATYPE_INT8_S, mCcaEnergyDetectThreshold)); + } + + if (mTransmitPowerSet) + { + SuccessOrDie(Set(SPINEL_PROP_PHY_TX_POWER, SPINEL_DATATYPE_INT8_S, mTransmitPower)); + } + + if (mCoexEnabledSet) + { + SuccessOrDie(Set(SPINEL_PROP_RADIO_COEX_ENABLE, SPINEL_DATATYPE_BOOL_S, mCoexEnabled)); + } + + if (mFemLnaGainSet) + { + SuccessOrDie(Set(SPINEL_PROP_PHY_FEM_LNA_GAIN, SPINEL_DATATYPE_INT8_S, mFemLnaGain)); + } + + CalcRcpTimeOffset(); +} +#endif // OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + } // namespace Spinel } // namespace ot diff --git a/src/lib/spinel/spinel.c b/src/lib/spinel/spinel.c index b4fe53ac5..3c180d38f 100644 --- a/src/lib/spinel/spinel.c +++ b/src/lib/spinel/spinel.c @@ -2251,6 +2251,14 @@ const char *spinel_prop_key_to_cstr(spinel_prop_key_t prop_key) ret = "DEBUG_TEST_WATCHDOG"; break; + case SPINEL_PROP_RCP_MAC_FRAME_COUNTER: + ret = "RCP_MAC_FRAME_COUNTER"; + break; + + case SPINEL_PROP_RCP_MAC_KEY: + ret = "RCP_MAC_KEY"; + break; + default: break; } diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index 0e779c7d1..04e097e5e 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -100,6 +100,8 @@ target_compile_definitions(openthread-posix ${OT_PUBLIC_DEFINES} PRIVATE ${OT_PLATFORM_DEFINES} + OPENTHREAD_FTD=1 + OPENTHREAD_MTD=0 ) target_compile_options(openthread-posix PRIVATE diff --git a/src/posix/platform/hdlc_interface.cpp b/src/posix/platform/hdlc_interface.cpp index 0ddc96733..e3ada77a4 100644 --- a/src/posix/platform/hdlc_interface.cpp +++ b/src/posix/platform/hdlc_interface.cpp @@ -135,6 +135,11 @@ HdlcInterface::HdlcInterface(SpinelInterface::ReceiveFrameCallback aCallback, { } +void HdlcInterface::OnRcpReset(void) +{ + mHdlcDecoder.Reset(); +} + otError HdlcInterface::Init(const RadioUrl &aRadioUrl) { otError error = OT_ERROR_NONE; diff --git a/src/posix/platform/hdlc_interface.hpp b/src/posix/platform/hdlc_interface.hpp index 8249eeed7..21333e3c0 100644 --- a/src/posix/platform/hdlc_interface.hpp +++ b/src/posix/platform/hdlc_interface.hpp @@ -156,6 +156,12 @@ public: */ uint32_t GetBusSpeed(void) const { return mBaudRate; } + /** + * This method is called when RCP failure detected and resets internal states of the interface. + * + */ + void OnRcpReset(void); + private: /** * 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 e59fd7557..689f4fe8e 100644 --- a/src/posix/platform/spi_interface.cpp +++ b/src/posix/platform/spi_interface.cpp @@ -91,6 +91,20 @@ SpiInterface::SpiInterface(SpinelInterface::ReceiveFrameCallback aCallback, { } +void SpiInterface::OnRcpReset(void) +{ + mSpiValidFrameCount = 0; + mSpiTxIsReady = false; + mSpiTxRefusedCount = 0; + mSpiTxPayloadSize = 0; + mDidPrintRateLimitLog = false; + mSpiSlaveDataLen = 0; + memset(mSpiTxFrameBuffer, 0, sizeof(mSpiTxFrameBuffer)); + + TriggerReset(); + usleep(static_cast(mSpiResetDelay) * kUsecPerMsec); +} + otError SpiInterface::Init(const RadioUrl &aRadioUrl) { const char *spiGpioIntDevice; @@ -155,6 +169,7 @@ otError SpiInterface::Init(const RadioUrl &aRadioUrl) VerifyOrDie(spiAlignAllowance <= kSpiAlignAllowanceMax, OT_EXIT_FAILURE); + mSpiResetDelay = spiResetDelay; mSpiCsDelayUs = spiCsDelay; mSpiSmallPacketSize = spiSmallPacketSize; mSpiAlignAllowance = spiAlignAllowance; @@ -173,7 +188,7 @@ otError SpiInterface::Init(const RadioUrl &aRadioUrl) InitSpiDev(aRadioUrl.GetPath(), spiMode, spiSpeed); // Reset RCP chip. - TrigerReset(); + TriggerReset(); // Waiting for the RCP chip starts up. usleep(static_cast(spiResetDelay) * kUsecPerMsec); @@ -318,7 +333,7 @@ exit: } } -void SpiInterface::TrigerReset(void) +void SpiInterface::TriggerReset(void) { // Set Reset pin to low level. SetGpioValue(mResetGpioValueFd, 0); diff --git a/src/posix/platform/spi_interface.hpp b/src/posix/platform/spi_interface.hpp index 06cdca7b4..d2d84a530 100644 --- a/src/posix/platform/spi_interface.hpp +++ b/src/posix/platform/spi_interface.hpp @@ -146,6 +146,12 @@ 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. + * + */ + void OnRcpReset(void); + private: 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); @@ -155,7 +161,7 @@ private: void InitResetPin(const char *aCharDev, uint8_t aLine); void InitIntPin(const char *aCharDev, uint8_t aLine); void InitSpiDev(const char *aPath, uint8_t aMode, uint32_t aSpeed); - void TrigerReset(void); + void TriggerReset(void); uint8_t *GetRealRxFrameStart(uint8_t *aSpiRxFrameBuffer, uint8_t aAlignAllowance, uint16_t &aSkipLength); otError DoSpiTransfer(uint8_t *aSpiRxFrameBuffer, uint32_t aTransferLength); @@ -208,6 +214,7 @@ private: uint8_t mSpiMode; uint8_t mSpiAlignAllowance; + uint32_t mSpiResetDelay; uint16_t mSpiCsDelayUs; uint16_t mSpiSmallPacketSize; uint32_t mSpiSpeedHz; diff --git a/tests/scripts/expect/_common.exp b/tests/scripts/expect/_common.exp index 54fe060a2..54423fa60 100644 --- a/tests/scripts/expect/_common.exp +++ b/tests/scripts/expect/_common.exp @@ -40,7 +40,7 @@ proc wait_for {command success {failure {[\r\n]FAILURE_NOT_EXPECTED[\r\n]}}} { set result 1 } -re $failure { - exit 1 + error "Got failure in wait_for" } timeout { # Do nothing @@ -51,11 +51,11 @@ proc wait_for {command success {failure {[\r\n]FAILURE_NOT_EXPECTED[\r\n]}}} { } } if {$result == 0} { - exit 1 + error "Timed out in wait_for" } } -proc spawn_node {id {type ""}} { +proc spawn_node {id {type ""} {radio_url ""}} { global spawn_id global spawn_ids @@ -63,26 +63,39 @@ proc spawn_node {id {type ""}} { set type $::env(OT_NODE_TYPE) } + if {${radio_url} == ""} { + set radio_url "spinel+hdlc+uart://$::env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=$id" + } + send_user "\n# ${id} ${type}\n" switch -regexp ${type} { {rcp|rcp-cli} { - spawn $::env(OT_POSIX_APPS)/ot-cli "spinel+hdlc+uart://$::env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=$id" + spawn $::env(OT_POSIX_APPS)/ot-cli $radio_url + send "factoryreset\n" + wait_for "state" "disabled" + expect "Done" send "routerselectionjitter 1\n" expect "Done" } cli { spawn $::env(OT_SIMULATION_APPS)/cli/ot-cli-ftd $id + send "factoryreset\n" + wait_for "state" "disabled" + expect "Done" send "routerselectionjitter 1\n" expect "Done" } mtd { spawn $::env(OT_SIMULATION_APPS)/cli/ot-cli-mtd $id + send "factoryreset\n" + wait_for "state" "disabled" + expect "Done" } } expect_after { - timeout { exit 1 } + timeout { error "Timed out" } } set spawn_ids($id) $spawn_id @@ -123,6 +136,7 @@ proc dispose_all {} { for {set i 1} {$i <= $max_node} {incr i} { dispose_node $i } + array unset spawn_ids } proc get_ipaddr {type} { diff --git a/tests/scripts/expect/_multinode.exp b/tests/scripts/expect/_multinode.exp index 4b7d84235..4d068d515 100644 --- a/tests/scripts/expect/_multinode.exp +++ b/tests/scripts/expect/_multinode.exp @@ -29,19 +29,27 @@ source "tests/scripts/expect/_common.exp" -proc setup_two_nodes {{childmode {r}}} { +proc setup_two_nodes {{childmode {r}} {should_spawn_node true}} { # Sets up a Thread network with 2 nodes, spawn_1 as the leader and spawn_2 # as a child. set psk "J01NME" - spawn_node 2 + if {$should_spawn_node} { + spawn_node 2 + } else { + switch_node 2 + } send "eui64\n" expect -re {([0-9a-f]{16})} set eui64 $expect_out(1,string) expect "Done" # Sets up a Thread network with node 1 as the leader. - spawn_node 1 + if {$should_spawn_node} { + spawn_node 1 + } else { + switch_node 1 + } setup_leader send "commissioner start\n" diff --git a/tests/scripts/expect/cli-big-table.exp b/tests/scripts/expect/cli-big-table.exp index b7d03077e..e8ae9b94b 100755 --- a/tests/scripts/expect/cli-big-table.exp +++ b/tests/scripts/expect/cli-big-table.exp @@ -31,9 +31,6 @@ source "tests/scripts/expect/_common.exp" set max_node 15 spawn_node 1 -expect_after { - timeout { exit 1 } -} send "panid 0xface\n" expect "Done" send "ifconfig up\n" @@ -45,9 +42,6 @@ expect "Done" for {set i 2} {$i <= $max_node} {incr i} { spawn_node $i - expect_after { - timeout { exit 1 } - } send "mode r\n" expect "Done" send "panid 0xface\n" @@ -61,9 +55,6 @@ for {set i 2} {$i <= $max_node} {incr i} { } set spawn_id $spawn_ids(1) -expect_after { - timeout { exit 1 } -} send "child table\n" expect "Done" diff --git a/tests/scripts/expect/posix-max-power-table.exp b/tests/scripts/expect/posix-max-power-table.exp index 239b12d08..4d5233964 100755 --- a/tests/scripts/expect/posix-max-power-table.exp +++ b/tests/scripts/expect/posix-max-power-table.exp @@ -30,10 +30,7 @@ source "tests/scripts/expect/_common.exp" # allows 11-25 and forbidden 26 -spawn $env(OT_POSIX_APPS)/ot-cli "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?max-power-table=11,12,13,14,15,16,17,18,19,20,21,22,23,24,-1,0x7f&forkpty-arg=1" -expect_after { - timeout { exit 1 } -} +spawn_node 1 "rcp" "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?max-power-table=11,12,13,14,15,16,17,18,19,20,21,22,23,24,-1,0x7f&forkpty-arg=1" send "channel supported\n" expect "0x3fff800" expect "Done" @@ -43,10 +40,7 @@ expect "Done" send "\x04" expect eof # allows all channels by default -spawn $env(OT_POSIX_APPS)/ot-cli "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=1" -expect_after { - timeout { exit 1 } -} +spawn_node 1 "rcp" "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=1" send "channel supported\n" expect "0x7fff800" expect "Done" diff --git a/tests/scripts/expect/posix-rcp-restoration.exp b/tests/scripts/expect/posix-rcp-restoration.exp new file mode 100755 index 000000000..19e1b2a2a --- /dev/null +++ b/tests/scripts/expect/posix-rcp-restoration.exp @@ -0,0 +1,306 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2020, 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. +# + +source "tests/scripts/expect/_common.exp" +source "tests/scripts/expect/_multinode.exp" + + +file tempfile socat_out +set socat_pid [exec socat -d -d pty,raw,echo=0 pty,raw,echo=0 >/dev/null 2>$socat_out &] +while {true} { + if {[exec head -n2 $socat_out | wc -l] == 2} { + set radio_pty [exec head -n1 $socat_out | grep -o {/dev/.\+}] + set host_pty [exec head -n2 $socat_out | tail -n1 | grep -o {/dev/.\+}] + break + } + sleep 1 +} + +puts "Radio PTY: $radio_pty" +puts "Host PTY: $host_pty" + +set rcp_pid [exec $::env(OT_SIMULATION_APPS)/ncp/ot-rcp 1 < $radio_pty > $radio_pty &] +puts "RCP PID: $rcp_pid" + +try { + puts "Before enabling" + + spawn_node 1 "rcp" "spinel+hdlc_uart://$host_pty" + + exec kill $rcp_pid + puts "Killed $rcp_pid" + sleep 1 + set rcp_pid [exec $::env(OT_SIMULATION_APPS)/ncp/ot-rcp 1 < $radio_pty > $radio_pty &] + puts "RCP PID: $rcp_pid" + + expect eof + + + puts "Queued parent-to-child packets, as parent" + + spawn_node 1 "rcp" "spinel+hdlc_uart://$host_pty" + spawn_node 2 + + setup_two_nodes "-" false + + switch_node 2 + send "ipaddr mleid\n" + expect "ipaddr mleid" + expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} + set addr_2 $expect_out(1,string) + send "udp open\n" + expect "Done" + send "udp bind :: 11003\n" + expect "Done" + send "pollperiod 100000\n" + expect "Done" + + sleep 1 + + switch_node 1 + send "ipaddr mleid\n" + expect "ipaddr mleid" + expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} + set addr_1 $expect_out(1,string) + send "udp open\n" + expect "Done" + send "udp bind :: 11004\n" + expect "Done" + send "udp connect $addr_2 11003\n" + expect "Done" + send "udp send hello\n" + expect "Done" + send "udp send there\n" + expect "Done" + + sleep 1 + + exec kill $rcp_pid + puts "Killed $rcp_pid" + sleep 1 + set rcp_pid [exec $::env(OT_SIMULATION_APPS)/ncp/ot-rcp 1 < $radio_pty > $radio_pty &] + puts "RCP PID: $rcp_pid" + + switch_node 2 + send "pollperiod 1000\n" + expect "Done" + expect "5 bytes from $addr_1 11004 hello" + expect "5 bytes from $addr_1 11004 there" + + dispose_all + + + puts "Queued parent-to-child packets, as child" + + exec kill $rcp_pid + puts "Killed $rcp_pid" + sleep 1 + set rcp_pid [exec $::env(OT_SIMULATION_APPS)/ncp/ot-rcp 2 < $radio_pty > $radio_pty &] + puts "RCP PID: $rcp_pid" + + spawn_node 1 + spawn_node 2 "rcp" "spinel+hdlc_uart://$host_pty" + + setup_two_nodes "-" false + + switch_node 2 + send "ipaddr mleid\n" + expect "ipaddr mleid" + expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} + set addr_2 $expect_out(1,string) + send "udp open\n" + expect "Done" + send "udp bind :: 11003\n" + expect "Done" + send "pollperiod 100000\n" + expect "Done" + + sleep 1 + + switch_node 1 + send "ipaddr mleid\n" + expect "ipaddr mleid" + expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} + set addr_1 $expect_out(1,string) + send "udp open\n" + expect "Done" + send "udp bind :: 11004\n" + expect "Done" + send "udp connect $addr_2 11003\n" + expect "Done" + send "udp send hello\n" + expect "Done" + + sleep 1 + + exec kill $rcp_pid + puts "Killed $rcp_pid" + sleep 1 + set rcp_pid [exec $::env(OT_SIMULATION_APPS)/ncp/ot-rcp 2 < $radio_pty > $radio_pty &] + puts "RCP PID: $rcp_pid" + + sleep 1 + + switch_node 2 + send "pollperiod 1000\n" + expect "Done" + expect "5 bytes from $addr_1 11004 hello" + + dispose_all + + exec kill $rcp_pid + puts "Killed $rcp_pid" + sleep 1 + set rcp_pid [exec $::env(OT_SIMULATION_APPS)/ncp/ot-rcp 1 < $radio_pty > $radio_pty &] + puts "RCP PID: $rcp_pid" + + + puts "Many children, queued child-to-child packets" + + spawn_node 1 "rcp" "spinel+hdlc_uart://$host_pty" + send "panid 0xface\n" + expect "Done" + send "ifconfig up\n" + expect "Done" + send "thread start\n" + expect "Done" + wait_for "state" "leader" + expect "Done" + send "ipaddr mleid\n" + expect "ipaddr mleid" + expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} + set addr(1) $expect_out(1,string) + send "udp open\n" + expect "Done" + send "udp bind :: 11004\n" + expect "Done" + + set max_children 15 + for {set i 2} {$i <= $max_children + 1} {incr i} { + spawn_node $i + send "mode -\n" + expect "Done" + send "panid 0xface\n" + expect "Done" + send "ifconfig up\n" + expect "Done" + send "thread start\n" + expect "Done" + wait_for "state" "child" + send "ipaddr mleid\n" + expect "ipaddr mleid" + expect -re {(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4})} + set addr($i) $expect_out(1,string) + expect "Done" + send "udp open\n" + expect "Done" + send "udp bind :: 11003\n" + expect "Done" + send "pollperiod 100000\n" + expect "Done" + } + + sleep 1 + + switch_node 1 + for {set i 2} {$i <= $max_children + 1} {incr i} { + send "udp send $addr($i) 11003 hello\n" + expect "Done" + } + + sleep 1 + + exec kill $rcp_pid + puts "Killed $rcp_pid" + sleep 1 + set rcp_pid [exec $::env(OT_SIMULATION_APPS)/ncp/ot-rcp 1 < $radio_pty > $radio_pty &] + puts "RCP PID: $rcp_pid" + + for {set i 7} {$i <= 9} {incr i} { + switch_node $i + send "pollperiod 1000\n" + expect "Done" + expect "5 bytes from $addr(1) 11004 hello" + } + + switch_node 4 + send "udp send $addr(5) 11003 hello_from_node_4\n" + expect "Done" + if {$::env(THREAD_VERSION) != "1.1"} { + expect "5 bytes from $addr(1) 11004 hello" + } + + sleep 1 + + exec kill $rcp_pid + puts "Killed $rcp_pid" + sleep 1 + set rcp_pid [exec $::env(OT_SIMULATION_APPS)/ncp/ot-rcp 1 < $radio_pty > $radio_pty &] + puts "RCP PID: $rcp_pid" + + switch_node 5 + send "pollperiod 1000\n" + expect "Done" + expect "17 bytes from $addr(4) 11003 hello_from_node_4" + + dispose_all + + + puts "While energy scanning" + + spawn_node 1 "rcp" "spinel+hdlc_uart://$host_pty" + send "ifconfig up\n" + expect "Done" + send "thread start\n" + expect "Done" + wait_for "state" "leader" + expect "Done" + + send "scan energy 100\n" + expect "| Ch | RSSI |" + expect "+----+------+" + + sleep 1 + + exec kill $rcp_pid + puts "Killed $rcp_pid" + sleep 1 + set rcp_pid [exec $::env(OT_SIMULATION_APPS)/ncp/ot-rcp 1 < $radio_pty > $radio_pty &] + puts "RCP PID: $rcp_pid" + + for {set i 11} {$i <= 26} {incr i} { + expect -re "\\| +$i \\| +-?\\d+ \\|" + } + expect "Done" + + dispose_all +} finally { + exec kill $rcp_pid + exec kill $socat_pid +} diff --git a/tests/scripts/expect/posix-scan-tx-to-sleep.exp b/tests/scripts/expect/posix-scan-tx-to-sleep.exp index e10896a22..7f2971479 100755 --- a/tests/scripts/expect/posix-scan-tx-to-sleep.exp +++ b/tests/scripts/expect/posix-scan-tx-to-sleep.exp @@ -29,12 +29,7 @@ source "tests/scripts/expect/_common.exp" -spawn $env(OT_POSIX_APPS)/ot-cli "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=--sleep-to-tx&forkpty-arg=1" -set node_1 $spawn_id -expect_after { - timeout { exit 1 } -} - +spawn_node 1 "rcp" "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=--sleep-to-tx&forkpty-arg=1" send "dataset init new\n" expect "Done" send "dataset panid 0xface\n" @@ -75,12 +70,7 @@ expect -re {(\d+)} set channel $expect_out(1,string) expect "Done" -spawn $env(OT_POSIX_APPS)/ot-cli "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=--sleep-to-tx&forkpty-arg=2" -set node_2 $spawn_id -expect_after { - timeout { exit 1 } -} - +spawn_node 2 "rcp" "spinel+hdlc+uart://$env(OT_SIMULATION_APPS)/ncp/ot-rcp?forkpty-arg=--sleep-to-tx&forkpty-arg=2" send "scan\n" expect "| J | Network Name | Extended PAN | PAN | MAC Address | Ch | dBm | LQI |" expect "+---+------------------+------------------+------+------------------+----+-----+-----+" diff --git a/tests/scripts/expect/v1_2-rcp-restoration.exp b/tests/scripts/expect/v1_2-rcp-restoration.exp new file mode 100644 index 000000000..6c8f61025 --- /dev/null +++ b/tests/scripts/expect/v1_2-rcp-restoration.exp @@ -0,0 +1,30 @@ +#!/usr/bin/expect -f +# +# Copyright (c) 2020, 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. +# + +source "tests/scripts/expect/posix-rcp-restoration.exp"