| /* |
| * 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. |
| */ |
| |
| /** |
| * @file |
| * This file implements the spinel based radio transceiver. |
| */ |
| |
| #include "radio_spinel.hpp" |
| |
| #include <assert.h> |
| #include <stdarg.h> |
| #include <stdlib.h> |
| |
| #include <openthread/link.h> |
| #include <openthread/logging.h> |
| #include <openthread/platform/diag.h> |
| #include <openthread/platform/time.h> |
| |
| #include "common/code_utils.hpp" |
| #include "common/new.hpp" |
| #include "lib/platform/exit_code.h" |
| #include "lib/spinel/logger.hpp" |
| #include "lib/spinel/spinel_driver.hpp" |
| #include "lib/spinel/spinel_helper.hpp" |
| |
| namespace ot { |
| namespace Spinel { |
| |
| otExtAddress RadioSpinel::sIeeeEui64; |
| |
| bool RadioSpinel::sSupportsLogStream = |
| false; ///< RCP supports `LOG_STREAM` property with OpenThread log meta-data format. |
| |
| bool RadioSpinel::sSupportsResetToBootloader = false; ///< RCP supports resetting into bootloader mode. |
| |
| bool RadioSpinel::sSupportsLogCrashDump = false; ///< RCP supports logging a crash dump. |
| |
| otRadioCaps RadioSpinel::sRadioCaps = OT_RADIO_CAPS_NONE; |
| |
| RadioSpinel::RadioSpinel(void) |
| : Logger("RadioSpinel") |
| , mInstance(nullptr) |
| , mCallbacks() |
| , mCmdTidsInUse(0) |
| , mCmdNextTid(1) |
| , mTxRadioTid(0) |
| , mWaitingTid(0) |
| , mWaitingKey(SPINEL_PROP_LAST_STATUS) |
| , mPropertyFormat(nullptr) |
| , mExpectedCommand(0) |
| , mError(OT_ERROR_NONE) |
| , mTransmitFrame(nullptr) |
| , mShortAddress(0) |
| , mPanId(0xffff) |
| , mChannel(0) |
| , mRxSensitivity(0) |
| , mBusLatency(0) |
| , mState(kStateDisabled) |
| , mIsPromiscuous(false) |
| , mRxOnWhenIdle(true) |
| , mIsTimeSynced(false) |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| , mRcpFailureCount(0) |
| , mRcpFailure(kRcpFailureNone) |
| , mSrcMatchShortEntryCount(0) |
| , mSrcMatchExtEntryCount(0) |
| , mSrcMatchEnabled(false) |
| , mRcpRestorationEnabled(true) |
| , mMacKeySet(false) |
| , mCcaEnergyDetectThresholdSet(false) |
| , mTransmitPowerSet(false) |
| , mMaxPowerTableSet(false) |
| , mCoexEnabledSet(false) |
| , mFemLnaGainSet(false) |
| , mEnergyScanning(false) |
| , mMacFrameCounterSet(false) |
| , mSrcMatchSet(false) |
| #endif |
| #if OPENTHREAD_CONFIG_DIAG_ENABLE |
| , mDiagMode(false) |
| , mOutputCallback(nullptr) |
| , mOutputContext(nullptr) |
| #endif |
| , mTxRadioEndUs(UINT64_MAX) |
| , mRadioTimeRecalcStart(UINT64_MAX) |
| , mRadioTimeOffset(UINT64_MAX) |
| , mMetrics(mCallbacks) |
| #if OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE |
| , mVendorRestorePropertiesCallback(nullptr) |
| , mVendorRestorePropertiesContext(nullptr) |
| #endif |
| #if OPENTHREAD_SPINEL_CONFIG_COMPATIBILITY_ERROR_CALLBACK_ENABLE |
| , mCompatibilityErrorCallback(nullptr) |
| , mCompatibilityErrorContext(nullptr) |
| #endif |
| , mTimeSyncEnabled(false) |
| , mTimeSyncOn(false) |
| , mSpinelDriver(nullptr) |
| { |
| memset(&mCallbacks, 0, sizeof(mCallbacks)); |
| } |
| |
| void RadioSpinel::Init(bool aSkipRcpVersionCheck, |
| bool aSoftwareReset, |
| SpinelDriver *aSpinelDriver, |
| otRadioCaps aRequiredRadioCaps, |
| bool aEnableRcpTimeSync) |
| { |
| otError error = OT_ERROR_NONE; |
| bool supportsRcpApiVersion; |
| bool supportsRcpMinHostApiVersion; |
| |
| OT_UNUSED_VARIABLE(aSoftwareReset); |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| mResetRadioOnStartup = aSoftwareReset; |
| #endif |
| |
| mTimeSyncEnabled = aEnableRcpTimeSync; |
| |
| mSpinelDriver = aSpinelDriver; |
| mSpinelDriver->SetFrameHandler(&HandleReceivedFrame, &HandleSavedFrame, this); |
| |
| #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_TIME_SYNC_ENABLE |
| memset(&mTxIeInfo, 0, sizeof(otRadioIeInfo)); |
| mTxRadioFrame.mInfo.mTxInfo.mIeInfo = &mTxIeInfo; |
| #endif |
| |
| SuccessOrExit(error = Get(SPINEL_PROP_HWADDR, SPINEL_DATATYPE_EUI64_S, sIeeeEui64.m8)); |
| InitializeCaps(supportsRcpApiVersion, supportsRcpMinHostApiVersion); |
| |
| if (sSupportsLogCrashDump) |
| { |
| LogDebg("RCP supports crash dump logging. Requesting crash dump."); |
| IgnoreReturnValue(Set(SPINEL_PROP_RCP_LOG_CRASH_DUMP, nullptr)); |
| } |
| |
| if (!aSkipRcpVersionCheck) |
| { |
| SuccessOrDie(CheckRcpApiVersion(supportsRcpApiVersion, supportsRcpMinHostApiVersion)); |
| } |
| |
| SuccessOrDie(CheckRadioCapabilities(aRequiredRadioCaps)); |
| |
| mRxRadioFrame.mPsdu = mRxPsdu; |
| mTxRadioFrame.mPsdu = mTxPsdu; |
| mAckRadioFrame.mPsdu = mAckPsdu; |
| |
| mMetrics.Init(); |
| |
| exit: |
| SuccessOrDie(error); |
| } |
| |
| void RadioSpinel::SetCallbacks(const struct RadioSpinelCallbacks &aCallbacks) |
| { |
| assert(aCallbacks.mEnergyScanDone != nullptr); |
| assert(aCallbacks.mReceiveDone != nullptr); |
| assert(aCallbacks.mTransmitDone != nullptr); |
| assert(aCallbacks.mTxStarted != nullptr); |
| |
| mCallbacks = aCallbacks; |
| } |
| |
| otError RadioSpinel::CheckSpinelVersion(void) |
| { |
| otError error = OT_ERROR_NONE; |
| unsigned int versionMajor; |
| unsigned int versionMinor; |
| |
| SuccessOrExit(error = |
| Get(SPINEL_PROP_PROTOCOL_VERSION, (SPINEL_DATATYPE_UINT_PACKED_S SPINEL_DATATYPE_UINT_PACKED_S), |
| &versionMajor, &versionMinor)); |
| |
| if ((versionMajor != SPINEL_PROTOCOL_VERSION_THREAD_MAJOR) || |
| (versionMinor != SPINEL_PROTOCOL_VERSION_THREAD_MINOR)) |
| { |
| LogCrit("Spinel version mismatch - Posix:%d.%d, RCP:%d.%d", SPINEL_PROTOCOL_VERSION_THREAD_MAJOR, |
| SPINEL_PROTOCOL_VERSION_THREAD_MINOR, versionMajor, versionMinor); |
| HandleCompatibilityError(); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void RadioSpinel::InitializeCaps(bool &aSupportsRcpApiVersion, bool &aSupportsRcpMinHostApiVersion) |
| { |
| if (!GetSpinelDriver().CoprocessorHasCap(SPINEL_CAP_CONFIG_RADIO)) |
| { |
| LogCrit("The co-processor isn't a RCP!"); |
| HandleCompatibilityError(); |
| } |
| |
| if (!GetSpinelDriver().CoprocessorHasCap(SPINEL_CAP_MAC_RAW)) |
| { |
| LogCrit("RCP capability list does not include support for radio/raw mode"); |
| HandleCompatibilityError(); |
| } |
| |
| sSupportsLogStream = GetSpinelDriver().CoprocessorHasCap(SPINEL_CAP_OPENTHREAD_LOG_METADATA); |
| aSupportsRcpApiVersion = GetSpinelDriver().CoprocessorHasCap(SPINEL_CAP_RCP_API_VERSION); |
| sSupportsResetToBootloader = GetSpinelDriver().CoprocessorHasCap(SPINEL_CAP_RCP_RESET_TO_BOOTLOADER); |
| aSupportsRcpMinHostApiVersion = GetSpinelDriver().CoprocessorHasCap(SPINEL_CAP_RCP_MIN_HOST_API_VERSION); |
| sSupportsLogCrashDump = GetSpinelDriver().CoprocessorHasCap(SPINEL_CAP_RCP_LOG_CRASH_DUMP); |
| } |
| |
| otError RadioSpinel::CheckRadioCapabilities(otRadioCaps aRequiredRadioCaps) |
| { |
| static const char *const kAllRadioCapsStr[] = {"ack-timeout", "energy-scan", "tx-retries", "CSMA-backoff", |
| "sleep-to-tx", "tx-security", "tx-timing", "rx-timing", |
| "rx-on-when-idle", "tx-frame-power"}; |
| |
| otError error = OT_ERROR_NONE; |
| unsigned int radioCaps; |
| |
| SuccessOrExit(error = Get(SPINEL_PROP_RADIO_CAPS, SPINEL_DATATYPE_UINT_PACKED_S, &radioCaps)); |
| sRadioCaps = static_cast<otRadioCaps>(radioCaps); |
| |
| if ((sRadioCaps & aRequiredRadioCaps) != aRequiredRadioCaps) |
| { |
| otRadioCaps missingCaps = (sRadioCaps & aRequiredRadioCaps) ^ aRequiredRadioCaps; |
| LogCrit("RCP is missing required capabilities: "); |
| |
| for (unsigned long i = 0; i < sizeof(kAllRadioCapsStr) / sizeof(kAllRadioCapsStr[0]); i++) |
| { |
| if (missingCaps & (1 << i)) |
| { |
| LogCrit(" %s", kAllRadioCapsStr[i]); |
| } |
| } |
| |
| HandleCompatibilityError(); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::CheckRcpApiVersion(bool aSupportsRcpApiVersion, bool aSupportsRcpMinHostApiVersion) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| static_assert(SPINEL_MIN_HOST_SUPPORTED_RCP_API_VERSION <= SPINEL_RCP_API_VERSION, |
| "MIN_HOST_SUPPORTED_RCP_API_VERSION must be smaller than or equal to RCP_API_VERSION"); |
| |
| if (aSupportsRcpApiVersion) |
| { |
| // Make sure RCP is not too old and its version is within the |
| // range host supports. |
| |
| unsigned int rcpApiVersion; |
| |
| SuccessOrExit(error = Get(SPINEL_PROP_RCP_API_VERSION, SPINEL_DATATYPE_UINT_PACKED_S, &rcpApiVersion)); |
| |
| if (rcpApiVersion < SPINEL_MIN_HOST_SUPPORTED_RCP_API_VERSION) |
| { |
| LogCrit("RCP and host are using incompatible API versions"); |
| LogCrit("RCP API Version %u is older than min required by host %u", rcpApiVersion, |
| SPINEL_MIN_HOST_SUPPORTED_RCP_API_VERSION); |
| HandleCompatibilityError(); |
| } |
| } |
| |
| if (aSupportsRcpMinHostApiVersion) |
| { |
| // Check with RCP about min host API version it can work with, |
| // and make sure on host side our version is within the supported |
| // range. |
| |
| unsigned int minHostRcpApiVersion; |
| |
| SuccessOrExit( |
| error = Get(SPINEL_PROP_RCP_MIN_HOST_API_VERSION, SPINEL_DATATYPE_UINT_PACKED_S, &minHostRcpApiVersion)); |
| |
| if (SPINEL_RCP_API_VERSION < minHostRcpApiVersion) |
| { |
| LogCrit("RCP and host are using incompatible API versions"); |
| LogCrit("RCP requires min host API version %u but host is older and at version %u", minHostRcpApiVersion, |
| SPINEL_RCP_API_VERSION); |
| HandleCompatibilityError(); |
| } |
| } |
| |
| exit: |
| return error; |
| } |
| |
| void RadioSpinel::Deinit(void) |
| { |
| // This allows implementing pseudo reset. |
| new (this) RadioSpinel(); |
| } |
| |
| void RadioSpinel::HandleNotification(const uint8_t *aFrame, uint16_t aLength, bool &aShouldSaveFrame) |
| { |
| spinel_prop_key_t key; |
| spinel_size_t len = 0; |
| spinel_ssize_t unpacked; |
| uint8_t *data = nullptr; |
| uint32_t cmd; |
| uint8_t header; |
| otError error = OT_ERROR_NONE; |
| |
| aShouldSaveFrame = false; |
| |
| unpacked = spinel_datatype_unpack(aFrame, aLength, "CiiD", &header, &cmd, &key, &data, &len); |
| |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| VerifyOrExit(SPINEL_HEADER_GET_TID(header) == 0, error = OT_ERROR_PARSE); |
| |
| switch (cmd) |
| { |
| case SPINEL_CMD_PROP_VALUE_IS: |
| // Some spinel properties cannot be handled during `WaitResponse()`, we must cache these events. |
| // `mWaitingTid` is released immediately after received the response. And `mWaitingKey` is be set |
| // to `SPINEL_PROP_LAST_STATUS` at the end of `WaitResponse()`. |
| |
| if (!IsSafeToHandleNow(key)) |
| { |
| ExitNow(aShouldSaveFrame = true); |
| } |
| |
| HandleValueIs(key, data, static_cast<uint16_t>(len)); |
| break; |
| |
| case SPINEL_CMD_PROP_VALUE_INSERTED: |
| case SPINEL_CMD_PROP_VALUE_REMOVED: |
| LogInfo("Ignored command %lu", ToUlong(cmd)); |
| break; |
| |
| default: |
| ExitNow(error = OT_ERROR_PARSE); |
| } |
| |
| exit: |
| |
| UpdateParseErrorCount(error); |
| LogIfFail("Error processing notification", error); |
| } |
| |
| void RadioSpinel::HandleNotification(const uint8_t *aFrame, uint16_t aLength) |
| { |
| spinel_prop_key_t key; |
| spinel_size_t len = 0; |
| spinel_ssize_t unpacked; |
| uint8_t *data = nullptr; |
| uint32_t cmd; |
| uint8_t header; |
| otError error = OT_ERROR_NONE; |
| |
| unpacked = spinel_datatype_unpack(aFrame, aLength, "CiiD", &header, &cmd, &key, &data, &len); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| VerifyOrExit(SPINEL_HEADER_GET_TID(header) == 0, error = OT_ERROR_PARSE); |
| VerifyOrExit(cmd == SPINEL_CMD_PROP_VALUE_IS); |
| HandleValueIs(key, data, static_cast<uint16_t>(len)); |
| |
| exit: |
| UpdateParseErrorCount(error); |
| LogIfFail("Error processing saved notification", error); |
| } |
| |
| void RadioSpinel::HandleResponse(const uint8_t *aBuffer, uint16_t aLength) |
| { |
| 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; |
| otError error = OT_ERROR_NONE; |
| |
| rval = spinel_datatype_unpack(aBuffer, 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); |
| |
| if (mWaitingTid == SPINEL_HEADER_GET_TID(header)) |
| { |
| HandleWaitingResponse(cmd, key, data, static_cast<uint16_t>(len)); |
| FreeTid(mWaitingTid); |
| mWaitingTid = 0; |
| } |
| else if (mTxRadioTid == SPINEL_HEADER_GET_TID(header)) |
| { |
| if (mState == kStateTransmitting) |
| { |
| HandleTransmitDone(cmd, key, data, static_cast<uint16_t>(len)); |
| } |
| |
| FreeTid(mTxRadioTid); |
| mTxRadioTid = 0; |
| } |
| else |
| { |
| LogWarn("Unexpected Spinel transaction message: %u", SPINEL_HEADER_GET_TID(header)); |
| error = OT_ERROR_DROP; |
| } |
| |
| exit: |
| UpdateParseErrorCount(error); |
| LogIfFail("Error processing response", error); |
| } |
| |
| void RadioSpinel::HandleWaitingResponse(uint32_t aCommand, |
| spinel_prop_key_t aKey, |
| const uint8_t *aBuffer, |
| uint16_t aLength) |
| { |
| if (aKey == SPINEL_PROP_LAST_STATUS) |
| { |
| spinel_status_t status; |
| spinel_ssize_t unpacked = spinel_datatype_unpack(aBuffer, aLength, "i", &status); |
| |
| VerifyOrExit(unpacked > 0, mError = OT_ERROR_PARSE); |
| mError = SpinelStatusToOtError(status); |
| } |
| #if OPENTHREAD_CONFIG_DIAG_ENABLE |
| else if (aKey == SPINEL_PROP_NEST_STREAM_MFG) |
| { |
| spinel_ssize_t unpacked; |
| const char *diagOutput; |
| |
| mError = OT_ERROR_NONE; |
| VerifyOrExit(mOutputCallback != nullptr); |
| unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_UTF8_S, &diagOutput); |
| VerifyOrExit(unpacked > 0, mError = OT_ERROR_PARSE); |
| PlatDiagOutput("%s", diagOutput); |
| } |
| #endif |
| else if (aKey == mWaitingKey) |
| { |
| if (mPropertyFormat) |
| { |
| if (static_cast<spinel_datatype_t>(mPropertyFormat[0]) == SPINEL_DATATYPE_VOID_C) |
| { |
| // reserved SPINEL_DATATYPE_VOID_C indicate caller want to parse the spinel response itself |
| ResponseHandler handler = va_arg(mPropertyArgs, ResponseHandler); |
| |
| assert(handler != nullptr); |
| mError = (this->*handler)(aBuffer, aLength); |
| } |
| else |
| { |
| spinel_ssize_t unpacked = |
| spinel_datatype_vunpack_in_place(aBuffer, aLength, mPropertyFormat, mPropertyArgs); |
| |
| VerifyOrExit(unpacked > 0, mError = OT_ERROR_PARSE); |
| mError = OT_ERROR_NONE; |
| } |
| } |
| else |
| { |
| if (aCommand == mExpectedCommand) |
| { |
| mError = OT_ERROR_NONE; |
| } |
| else |
| { |
| mError = OT_ERROR_DROP; |
| } |
| } |
| } |
| else |
| { |
| mError = OT_ERROR_DROP; |
| } |
| |
| exit: |
| UpdateParseErrorCount(mError); |
| LogIfFail("Error processing result", mError); |
| } |
| |
| void RadioSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength) |
| { |
| otError error = OT_ERROR_NONE; |
| spinel_ssize_t unpacked; |
| |
| if (aKey == SPINEL_PROP_STREAM_RAW) |
| { |
| SuccessOrExit(error = ParseRadioFrame(mRxRadioFrame, aBuffer, aLength, unpacked)); |
| RadioReceive(); |
| } |
| else if (aKey == SPINEL_PROP_LAST_STATUS) |
| { |
| spinel_status_t status = SPINEL_STATUS_OK; |
| |
| unpacked = spinel_datatype_unpack(aBuffer, aLength, "i", &status); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| |
| if (status >= SPINEL_STATUS_RESET__BEGIN && status <= SPINEL_STATUS_RESET__END) |
| { |
| if (IsEnabled()) |
| { |
| HandleRcpUnexpectedReset(status); |
| ExitNow(); |
| } |
| |
| // this clear is necessary in case the RCP has sent messages between disable and reset |
| mSpinelDriver->ClearRxBuffer(); |
| mSpinelDriver->SetCoprocessorReady(); |
| |
| LogInfo("RCP reset: %s", spinel_status_to_cstr(status)); |
| } |
| else if (status == SPINEL_STATUS_SWITCHOVER_DONE || status == SPINEL_STATUS_SWITCHOVER_FAILED) |
| { |
| if (mCallbacks.mSwitchoverDone != nullptr) |
| { |
| mCallbacks.mSwitchoverDone(mInstance, status == SPINEL_STATUS_SWITCHOVER_DONE); |
| } |
| } |
| else |
| { |
| LogInfo("RCP last status: %s", spinel_status_to_cstr(status)); |
| } |
| } |
| else if (aKey == SPINEL_PROP_MAC_ENERGY_SCAN_RESULT) |
| { |
| uint8_t scanChannel; |
| int8_t maxRssi; |
| |
| 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 |
| |
| mCallbacks.mEnergyScanDone(mInstance, maxRssi); |
| } |
| else if (aKey == SPINEL_PROP_STREAM_DEBUG) |
| { |
| char logStream[OPENTHREAD_CONFIG_NCP_SPINEL_LOG_MAX_SIZE + 1]; |
| unsigned int len = sizeof(logStream) - 1; |
| |
| unpacked = spinel_datatype_unpack_in_place(aBuffer, aLength, SPINEL_DATATYPE_DATA_S, logStream, &len); |
| assert(len < sizeof(logStream)); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| logStream[len] = '\0'; |
| LogDebg("RCP => %s", logStream); |
| } |
| else if ((aKey == SPINEL_PROP_STREAM_LOG) && sSupportsLogStream) |
| { |
| const char *logString; |
| uint8_t logLevel; |
| |
| unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_UTF8_S, &logString); |
| VerifyOrExit(unpacked >= 0, error = OT_ERROR_PARSE); |
| aBuffer += unpacked; |
| aLength -= unpacked; |
| |
| unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_UINT8_S, &logLevel); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| |
| switch (logLevel) |
| { |
| case SPINEL_NCP_LOG_LEVEL_EMERG: |
| case SPINEL_NCP_LOG_LEVEL_ALERT: |
| case SPINEL_NCP_LOG_LEVEL_CRIT: |
| LogCrit("RCP => %s", logString); |
| break; |
| |
| case SPINEL_NCP_LOG_LEVEL_ERR: |
| case SPINEL_NCP_LOG_LEVEL_WARN: |
| LogWarn("RCP => %s", logString); |
| break; |
| |
| case SPINEL_NCP_LOG_LEVEL_NOTICE: |
| LogNote("RCP => %s", logString); |
| break; |
| |
| case SPINEL_NCP_LOG_LEVEL_INFO: |
| LogInfo("RCP => %s", logString); |
| break; |
| |
| case SPINEL_NCP_LOG_LEVEL_DEBUG: |
| default: |
| LogDebg("RCP => %s", logString); |
| break; |
| } |
| } |
| #if OPENTHREAD_CONFIG_DIAG_ENABLE |
| else if (aKey == SPINEL_PROP_NEST_STREAM_MFG) |
| { |
| const char *diagOutput; |
| |
| VerifyOrExit(mOutputCallback != nullptr); |
| unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_UTF8_S, &diagOutput); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| PlatDiagOutput("%s", diagOutput); |
| } |
| #endif |
| #if OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE |
| else if (aKey >= SPINEL_PROP_VENDOR__BEGIN && aKey < SPINEL_PROP_VENDOR__END) |
| { |
| error = VendorHandleValueIs(aKey, aBuffer, aLength); |
| } |
| #endif |
| |
| exit: |
| UpdateParseErrorCount(error); |
| LogIfFail("Failed to handle ValueIs", error); |
| } |
| |
| #if OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE |
| void RadioSpinel::SetVendorRestorePropertiesCallback(otRadioSpinelVendorRestorePropertiesCallback aCallback, |
| void *aContext) |
| { |
| mVendorRestorePropertiesCallback = aCallback; |
| mVendorRestorePropertiesContext = aContext; |
| } |
| #endif |
| |
| SpinelDriver &RadioSpinel::GetSpinelDriver(void) const |
| { |
| OT_ASSERT(mSpinelDriver != nullptr); |
| return *mSpinelDriver; |
| } |
| |
| otError RadioSpinel::SendReset(uint8_t aResetType) |
| { |
| otError error; |
| |
| if ((aResetType == SPINEL_RESET_BOOTLOADER) && !sSupportsResetToBootloader) |
| { |
| ExitNow(error = OT_ERROR_NOT_CAPABLE); |
| } |
| error = GetSpinelDriver().SendReset(aResetType); |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::ParseRadioFrame(otRadioFrame &aFrame, |
| const uint8_t *aBuffer, |
| uint16_t aLength, |
| spinel_ssize_t &aUnpacked) |
| { |
| otError error = OT_ERROR_NONE; |
| uint16_t flags = 0; |
| int8_t noiseFloor = -128; |
| spinel_size_t size = OT_RADIO_FRAME_MAX_SIZE; |
| unsigned int receiveError = 0; |
| spinel_ssize_t unpacked; |
| |
| VerifyOrExit(aLength > 0, aFrame.mLength = 0); |
| |
| unpacked = spinel_datatype_unpack_in_place(aBuffer, aLength, |
| SPINEL_DATATYPE_DATA_WLEN_S // Frame |
| SPINEL_DATATYPE_INT8_S // RSSI |
| SPINEL_DATATYPE_INT8_S // Noise Floor |
| SPINEL_DATATYPE_UINT16_S // Flags |
| SPINEL_DATATYPE_STRUCT_S( // PHY-data |
| SPINEL_DATATYPE_UINT8_S // 802.15.4 channel |
| SPINEL_DATATYPE_UINT8_S // 802.15.4 LQI |
| SPINEL_DATATYPE_UINT64_S // Timestamp (us). |
| ) SPINEL_DATATYPE_STRUCT_S( // Vendor-data |
| SPINEL_DATATYPE_UINT_PACKED_S // Receive error |
| ), |
| aFrame.mPsdu, &size, &aFrame.mInfo.mRxInfo.mRssi, &noiseFloor, &flags, |
| &aFrame.mChannel, &aFrame.mInfo.mRxInfo.mLqi, |
| &aFrame.mInfo.mRxInfo.mTimestamp, &receiveError); |
| |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| aUnpacked = unpacked; |
| |
| aBuffer += unpacked; |
| aLength -= static_cast<uint16_t>(unpacked); |
| |
| if (sRadioCaps & OT_RADIO_CAPS_TRANSMIT_SEC) |
| { |
| unpacked = |
| spinel_datatype_unpack_in_place(aBuffer, aLength, |
| SPINEL_DATATYPE_STRUCT_S( // MAC-data |
| SPINEL_DATATYPE_UINT8_S // Security key index |
| SPINEL_DATATYPE_UINT32_S // Security frame counter |
| ), |
| &aFrame.mInfo.mRxInfo.mAckKeyId, &aFrame.mInfo.mRxInfo.mAckFrameCounter); |
| |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| aUnpacked += unpacked; |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| if (flags & SPINEL_MD_FLAG_ACKED_SEC) |
| { |
| mMacFrameCounterSet = true; |
| } |
| #endif |
| } |
| |
| if (receiveError == OT_ERROR_NONE) |
| { |
| aFrame.mLength = static_cast<uint8_t>(size); |
| |
| aFrame.mInfo.mRxInfo.mAckedWithFramePending = ((flags & SPINEL_MD_FLAG_ACKED_FP) != 0); |
| aFrame.mInfo.mRxInfo.mAckedWithSecEnhAck = ((flags & SPINEL_MD_FLAG_ACKED_SEC) != 0); |
| } |
| else if (receiveError < OT_NUM_ERRORS) |
| { |
| error = static_cast<otError>(receiveError); |
| } |
| else |
| { |
| error = OT_ERROR_PARSE; |
| } |
| |
| exit: |
| UpdateParseErrorCount(error); |
| LogIfFail("Handle radio frame failed", error); |
| return error; |
| } |
| |
| void RadioSpinel::RadioReceive(void) |
| { |
| if (!mIsPromiscuous) |
| { |
| switch (mState) |
| { |
| case kStateDisabled: |
| case kStateSleep: |
| ExitNow(); |
| |
| case kStateReceive: |
| case kStateTransmitting: |
| case kStateTransmitDone: |
| break; |
| } |
| } |
| |
| mCallbacks.mReceiveDone(mInstance, &mRxRadioFrame, OT_ERROR_NONE); |
| exit: |
| return; |
| } |
| |
| void RadioSpinel::TransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError) |
| { |
| mCallbacks.mTransmitDone(mInstance, aFrame, aAckFrame, aError); |
| } |
| |
| void RadioSpinel::ProcessRadioStateMachine(void) |
| { |
| if (mState == kStateTransmitDone) |
| { |
| mState = kStateReceive; |
| mTxRadioEndUs = UINT64_MAX; |
| |
| TransmitDone(mTransmitFrame, (mAckRadioFrame.mLength != 0) ? &mAckRadioFrame : nullptr, mTxError); |
| } |
| else if (mState == kStateTransmitting && otPlatTimeGet() >= mTxRadioEndUs) |
| { |
| // Frame has been successfully passed to radio, but no `TransmitDone` event received within kTxWaitUs. |
| LogWarn("radio tx timeout"); |
| HandleRcpTimeout(); |
| } |
| } |
| |
| void RadioSpinel::Process(const void *aContext) |
| { |
| OT_UNUSED_VARIABLE(aContext); |
| |
| ProcessRadioStateMachine(); |
| RecoverFromRcpFailure(); |
| |
| if (mTimeSyncEnabled) |
| { |
| CalcRcpTimeOffset(); |
| } |
| } |
| |
| otError RadioSpinel::SetPromiscuous(bool aEnable) |
| { |
| otError error; |
| |
| uint8_t mode = (aEnable ? SPINEL_MAC_PROMISCUOUS_MODE_NETWORK : SPINEL_MAC_PROMISCUOUS_MODE_OFF); |
| SuccessOrExit(error = Set(SPINEL_PROP_MAC_PROMISCUOUS_MODE, SPINEL_DATATYPE_UINT8_S, mode)); |
| mIsPromiscuous = aEnable; |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::SetRxOnWhenIdle(bool aEnable) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(mRxOnWhenIdle != aEnable); |
| SuccessOrExit(error = Set(SPINEL_PROP_MAC_RX_ON_WHEN_IDLE_MODE, SPINEL_DATATYPE_BOOL_S, aEnable)); |
| mRxOnWhenIdle = aEnable; |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::SetShortAddress(uint16_t aAddress) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(mShortAddress != aAddress); |
| SuccessOrExit(error = Set(SPINEL_PROP_MAC_15_4_SADDR, SPINEL_DATATYPE_UINT16_S, aAddress)); |
| mShortAddress = aAddress; |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::SetAlternateShortAddress(uint16_t aAddress) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(sRadioCaps & OT_RADIO_CAPS_ALT_SHORT_ADDR); |
| error = Set(SPINEL_PROP_MAC_15_4_ALT_SADDR, SPINEL_DATATYPE_UINT16_S, aAddress); |
| |
| exit: |
| return error; |
| } |
| |
| #if OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE |
| |
| otError RadioSpinel::ReadMacKey(const otMacKeyMaterial &aKeyMaterial, otMacKey &aKey) |
| { |
| size_t keySize; |
| otError error = otPlatCryptoExportKey(aKeyMaterial.mKeyMaterial.mKeyRef, aKey.m8, sizeof(aKey), &keySize); |
| |
| SuccessOrExit(error); |
| VerifyOrExit(keySize == sizeof(otMacKey), error = OT_ERROR_FAILED); |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::SetMacKey(uint8_t aKeyIdMode, |
| uint8_t aKeyIndex, |
| const otMacKeyMaterial *aPrevKey, |
| const otMacKeyMaterial *aCurrKey, |
| const otMacKeyMaterial *aNextKey) |
| { |
| otError error; |
| otMacKey prevKey; |
| otMacKey currKey; |
| otMacKey nextKey; |
| |
| SuccessOrExit(error = ReadMacKey(*aPrevKey, prevKey)); |
| SuccessOrExit(error = ReadMacKey(*aCurrKey, currKey)); |
| SuccessOrExit(error = ReadMacKey(*aNextKey, nextKey)); |
| error = SetMacKey(aKeyIdMode, aKeyIndex, prevKey, currKey, nextKey); |
| |
| exit: |
| return error; |
| } |
| |
| #else |
| |
| otError RadioSpinel::SetMacKey(uint8_t aKeyIdMode, |
| uint8_t aKeyIndex, |
| const otMacKeyMaterial *aPrevKey, |
| const otMacKeyMaterial *aCurrKey, |
| const otMacKeyMaterial *aNextKey) |
| { |
| return SetMacKey(aKeyIdMode, aKeyIndex, aPrevKey->mKeyMaterial.mKey, aCurrKey->mKeyMaterial.mKey, |
| aNextKey->mKeyMaterial.mKey); |
| } |
| |
| #endif // OPENTHREAD_CONFIG_PLATFORM_KEY_REFERENCES_ENABLE |
| |
| otError RadioSpinel::SetMacKey(uint8_t aKeyIdMode, |
| uint8_t aKeyIndex, |
| const otMacKey &aPrevKey, |
| const otMacKey &aCurrKey, |
| const otMacKey &aNextKey) |
| { |
| otError error; |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_KEY_ID_MODE_CHECK_COMPATIBILITY_WORKAROUND_ENABLE |
| static constexpr uint8_t kLegacyKeyIdMode1 = (1 << 3); |
| |
| // Older RCP builds enforce a validation check in `NcpBase` |
| // (`HandlePropertySet<SPINEL_PROP_RCP_MAC_KEY>()`) expecting the |
| // legacy bit-shifted value `(1 << 3)` for Key ID Mode 1. This |
| // check is removed so future RCP builds ignore `aKeyIdMode` |
| // as documented/expected for the `otPlatRadioSetMacKey()` API. |
| // |
| // To maintain backward compatibility with older RCP firmware |
| // builds, we map `aKeyIdMode` to the legacy bit-shifted value |
| // `kLegacyKeyIdMode1` when setting `SPINEL_PROP_RCP_MAC_KEY`. |
| |
| aKeyIdMode = kLegacyKeyIdMode1; |
| #endif |
| |
| SuccessOrExit(error = 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, |
| aKeyIdMode, aKeyIndex, aPrevKey.m8, sizeof(aPrevKey), aCurrKey.m8, sizeof(aCurrKey), |
| aNextKey.m8, sizeof(aNextKey))); |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| mKeyIdMode = aKeyIdMode; |
| mKeyIndex = aKeyIndex; |
| |
| mPrevKey = aPrevKey; |
| mCurrKey = aCurrKey; |
| mNextKey = aNextKey; |
| |
| mMacKeySet = true; |
| #endif |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::SetMacFrameCounter(uint32_t aMacFrameCounter, bool aSetIfLarger) |
| { |
| otError error; |
| |
| SuccessOrExit(error = Set(SPINEL_PROP_RCP_MAC_FRAME_COUNTER, SPINEL_DATATYPE_UINT32_S SPINEL_DATATYPE_BOOL_S, |
| aMacFrameCounter, aSetIfLarger)); |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| mMacFrameCounterSet = true; |
| #endif |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::GetIeeeEui64(uint8_t *aIeeeEui64) |
| { |
| memcpy(aIeeeEui64, sIeeeEui64.m8, sizeof(sIeeeEui64.m8)); |
| |
| return OT_ERROR_NONE; |
| } |
| |
| otError RadioSpinel::SetExtendedAddress(const otExtAddress &aExtAddress) |
| { |
| otError error; |
| |
| SuccessOrExit(error = Set(SPINEL_PROP_MAC_15_4_LADDR, SPINEL_DATATYPE_EUI64_S, aExtAddress.m8)); |
| mExtendedAddress = aExtAddress; |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::SetPanId(uint16_t aPanId) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(mPanId != aPanId); |
| SuccessOrExit(error = Set(SPINEL_PROP_MAC_15_4_PANID, SPINEL_DATATYPE_UINT16_S, aPanId)); |
| mPanId = aPanId; |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::EnableSrcMatch(bool aEnable) |
| { |
| otError error; |
| |
| SuccessOrExit(error = Set(SPINEL_PROP_MAC_SRC_MATCH_ENABLED, SPINEL_DATATYPE_BOOL_S, aEnable)); |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| mSrcMatchSet = true; |
| mSrcMatchEnabled = aEnable; |
| #endif |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::AddSrcMatchShortEntry(uint16_t aShortAddress) |
| { |
| otError error; |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| VerifyOrExit(mSrcMatchShortEntryCount < OPENTHREAD_SPINEL_CONFIG_MAX_SRC_MATCH_ENTRIES, error = OT_ERROR_NO_BUFS); |
| #endif |
| |
| SuccessOrExit(error = Insert(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) |
| { |
| ExitNow(); |
| } |
| } |
| mSrcMatchShortEntries[mSrcMatchShortEntryCount] = aShortAddress; |
| ++mSrcMatchShortEntryCount; |
| #endif |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::AddSrcMatchExtEntry(const otExtAddress &aExtAddress) |
| { |
| otError error; |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| VerifyOrExit(mSrcMatchExtEntryCount < OPENTHREAD_SPINEL_CONFIG_MAX_SRC_MATCH_ENTRIES, error = OT_ERROR_NO_BUFS); |
| #endif |
| |
| 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 |
| |
| 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; |
| } |
| |
| otError RadioSpinel::ClearSrcMatchShortEntry(uint16_t 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; |
| } |
| |
| otError RadioSpinel::ClearSrcMatchExtEntry(const otExtAddress &aExtAddress) |
| { |
| 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; |
| } |
| |
| otError RadioSpinel::ClearSrcMatchShortEntries(void) |
| { |
| 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; |
| } |
| |
| otError RadioSpinel::ClearSrcMatchExtEntries(void) |
| { |
| 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; |
| } |
| |
| otError RadioSpinel::GetTransmitPower(int8_t &aPower) |
| { |
| otError error = Get(SPINEL_PROP_PHY_TX_POWER, SPINEL_DATATYPE_INT8_S, &aPower); |
| |
| LogIfFail("Get transmit power failed", error); |
| return error; |
| } |
| |
| otError RadioSpinel::GetCcaEnergyDetectThreshold(int8_t &aThreshold) |
| { |
| otError error = Get(SPINEL_PROP_PHY_CCA_THRESHOLD, SPINEL_DATATYPE_INT8_S, &aThreshold); |
| |
| LogIfFail("Get CCA ED threshold failed", error); |
| return error; |
| } |
| |
| otError RadioSpinel::GetFemLnaGain(int8_t &aGain) |
| { |
| otError error = Get(SPINEL_PROP_PHY_FEM_LNA_GAIN, SPINEL_DATATYPE_INT8_S, &aGain); |
| |
| LogIfFail("Get FEM LNA gain failed", error); |
| return error; |
| } |
| |
| int8_t RadioSpinel::GetRssi(void) |
| { |
| int8_t rssi = OT_RADIO_RSSI_INVALID; |
| otError error = Get(SPINEL_PROP_PHY_RSSI, SPINEL_DATATYPE_INT8_S, &rssi); |
| |
| LogIfFail("Get RSSI failed", error); |
| return rssi; |
| } |
| |
| #if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE |
| otError RadioSpinel::SetCoexEnabled(bool 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; |
| } |
| |
| bool RadioSpinel::IsCoexEnabled(void) |
| { |
| bool enabled; |
| otError error = Get(SPINEL_PROP_RADIO_COEX_ENABLE, SPINEL_DATATYPE_BOOL_S, &enabled); |
| |
| LogIfFail("Get Coex State failed", error); |
| return enabled; |
| } |
| |
| otError RadioSpinel::GetCoexMetrics(otRadioCoexMetrics &aCoexMetrics) |
| { |
| otError error; |
| |
| error = Get(SPINEL_PROP_RADIO_COEX_METRICS, |
| SPINEL_DATATYPE_STRUCT_S( // Tx Coex Metrics Structure |
| SPINEL_DATATYPE_UINT32_S // NumTxRequest |
| SPINEL_DATATYPE_UINT32_S // NumTxGrantImmediate |
| SPINEL_DATATYPE_UINT32_S // NumTxGrantWait |
| SPINEL_DATATYPE_UINT32_S // NumTxGrantWaitActivated |
| SPINEL_DATATYPE_UINT32_S // NumTxGrantWaitTimeout |
| SPINEL_DATATYPE_UINT32_S // NumTxGrantDeactivatedDuringRequest |
| SPINEL_DATATYPE_UINT32_S // NumTxDelayedGrant |
| SPINEL_DATATYPE_UINT32_S // AvgTxRequestToGrantTime |
| ) SPINEL_DATATYPE_STRUCT_S( // Rx Coex Metrics Structure |
| SPINEL_DATATYPE_UINT32_S // NumRxRequest |
| SPINEL_DATATYPE_UINT32_S // NumRxGrantImmediate |
| SPINEL_DATATYPE_UINT32_S // NumRxGrantWait |
| SPINEL_DATATYPE_UINT32_S // NumRxGrantWaitActivated |
| SPINEL_DATATYPE_UINT32_S // NumRxGrantWaitTimeout |
| SPINEL_DATATYPE_UINT32_S // NumRxGrantDeactivatedDuringRequest |
| SPINEL_DATATYPE_UINT32_S // NumRxDelayedGrant |
| SPINEL_DATATYPE_UINT32_S // AvgRxRequestToGrantTime |
| SPINEL_DATATYPE_UINT32_S // NumRxGrantNone |
| ) SPINEL_DATATYPE_BOOL_S // Stopped |
| SPINEL_DATATYPE_UINT32_S, // NumGrantGlitch |
| &aCoexMetrics.mNumTxRequest, &aCoexMetrics.mNumTxGrantImmediate, &aCoexMetrics.mNumTxGrantWait, |
| &aCoexMetrics.mNumTxGrantWaitActivated, &aCoexMetrics.mNumTxGrantWaitTimeout, |
| &aCoexMetrics.mNumTxGrantDeactivatedDuringRequest, &aCoexMetrics.mNumTxDelayedGrant, |
| &aCoexMetrics.mAvgTxRequestToGrantTime, &aCoexMetrics.mNumRxRequest, &aCoexMetrics.mNumRxGrantImmediate, |
| &aCoexMetrics.mNumRxGrantWait, &aCoexMetrics.mNumRxGrantWaitActivated, |
| &aCoexMetrics.mNumRxGrantWaitTimeout, &aCoexMetrics.mNumRxGrantDeactivatedDuringRequest, |
| &aCoexMetrics.mNumRxDelayedGrant, &aCoexMetrics.mAvgRxRequestToGrantTime, &aCoexMetrics.mNumRxGrantNone, |
| &aCoexMetrics.mStopped, &aCoexMetrics.mNumGrantGlitch); |
| |
| LogIfFail("Get Coex Metrics failed", error); |
| return error; |
| } |
| #endif |
| |
| otError RadioSpinel::SetTransmitPower(int8_t 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; |
| } |
| |
| otError RadioSpinel::SetCcaEnergyDetectThreshold(int8_t 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; |
| } |
| |
| otError RadioSpinel::SetFemLnaGain(int8_t 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; |
| } |
| |
| otError RadioSpinel::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration) |
| { |
| otError error; |
| |
| VerifyOrExit(sRadioCaps & 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)); |
| |
| mChannel = aScanChannel; |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::Get(spinel_prop_key_t aKey, const char *aFormat, ...) |
| { |
| otError error; |
| |
| assert(mWaitingTid == 0); |
| |
| #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 (mRcpFailure != kRcpFailureNone); |
| #endif |
| |
| return error; |
| } |
| |
| // This is not a normal use case for VALUE_GET command and should be only used to get RCP timestamp with dummy payload |
| otError RadioSpinel::GetWithParam(spinel_prop_key_t aKey, |
| const uint8_t *aParam, |
| spinel_size_t aParamSize, |
| const char *aFormat, |
| ...) |
| { |
| otError error; |
| |
| assert(mWaitingTid == 0); |
| |
| #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 (mRcpFailure != kRcpFailureNone); |
| #endif |
| |
| return error; |
| } |
| |
| otError RadioSpinel::Set(spinel_prop_key_t aKey, const char *aFormat, ...) |
| { |
| otError error; |
| |
| assert(mWaitingTid == 0); |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| do |
| { |
| if (aKey != SPINEL_PROP_RCP_LOG_CRASH_DUMP) |
| { |
| 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 (aKey != SPINEL_PROP_RCP_LOG_CRASH_DUMP && mRcpFailure != kRcpFailureNone); |
| #endif |
| |
| return error; |
| } |
| |
| otError RadioSpinel::Insert(spinel_prop_key_t aKey, const char *aFormat, ...) |
| { |
| otError error; |
| |
| assert(mWaitingTid == 0); |
| |
| #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 (mRcpFailure != kRcpFailureNone); |
| #endif |
| |
| return error; |
| } |
| |
| otError RadioSpinel::Remove(spinel_prop_key_t aKey, const char *aFormat, ...) |
| { |
| otError error; |
| |
| assert(mWaitingTid == 0); |
| |
| #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 (mRcpFailure != kRcpFailureNone); |
| #endif |
| |
| return error; |
| } |
| |
| otError RadioSpinel::WaitResponse(bool aHandleRcpTimeout) |
| { |
| uint64_t end = otPlatTimeGet() + kMaxWaitTime * kUsPerMs; |
| |
| LogDebg("Wait response: tid=%u key=%lu", mWaitingTid, ToUlong(mWaitingKey)); |
| |
| do |
| { |
| uint64_t now; |
| |
| now = otPlatTimeGet(); |
| if ((end <= now) || (GetSpinelDriver().GetSpinelInterface()->WaitForFrame(end - now) != OT_ERROR_NONE)) |
| { |
| LogWarn("Wait for response timeout"); |
| // Skip RCP timeout handling for the non-essential crash dump property |
| if (aHandleRcpTimeout && mWaitingKey != SPINEL_PROP_RCP_LOG_CRASH_DUMP) |
| { |
| HandleRcpTimeout(); |
| } |
| ExitNow(mError = OT_ERROR_RESPONSE_TIMEOUT); |
| } |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| if (mRcpFailure != kRcpFailureNone) |
| { |
| ExitNow(mError = OT_ERROR_RESPONSE_TIMEOUT); |
| } |
| #endif |
| } while (mWaitingTid); |
| |
| LogIfFail("Error waiting response", mError); |
| // This indicates end of waiting response. |
| mWaitingKey = SPINEL_PROP_LAST_STATUS; |
| |
| exit: |
| if (mError != OT_ERROR_NONE && mWaitingTid != 0) |
| { |
| FreeTid(mWaitingTid); |
| mWaitingTid = 0; |
| } |
| return mError; |
| } |
| |
| spinel_tid_t RadioSpinel::GetNextTid(void) |
| { |
| spinel_tid_t tid = mCmdNextTid; |
| |
| while (((1 << tid) & mCmdTidsInUse) != 0) |
| { |
| tid = SPINEL_GET_NEXT_TID(tid); |
| |
| if (tid == mCmdNextTid) |
| { |
| // We looped back to `mCmdNextTid` indicating that all |
| // TIDs are in-use. |
| |
| ExitNow(tid = 0); |
| } |
| } |
| |
| mCmdTidsInUse |= (1 << tid); |
| mCmdNextTid = SPINEL_GET_NEXT_TID(tid); |
| |
| exit: |
| return tid; |
| } |
| |
| otError RadioSpinel::RequestV(uint32_t command, spinel_prop_key_t aKey, const char *aFormat, va_list aArgs) |
| { |
| otError error = OT_ERROR_NONE; |
| spinel_tid_t tid = GetNextTid(); |
| |
| VerifyOrExit(tid > 0, error = OT_ERROR_BUSY); |
| |
| error = GetSpinelDriver().SendCommand(command, aKey, tid, aFormat, aArgs); |
| SuccessOrExit(error); |
| |
| if (aKey == SPINEL_PROP_STREAM_RAW) |
| { |
| // not allowed to send another frame before the last frame is done. |
| assert(mTxRadioTid == 0); |
| VerifyOrExit(mTxRadioTid == 0, error = OT_ERROR_BUSY); |
| mTxRadioTid = tid; |
| } |
| else |
| { |
| mWaitingKey = aKey; |
| mWaitingTid = tid; |
| error = WaitResponse(); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::Request(uint32_t aCommand, spinel_prop_key_t aKey, const char *aFormat, ...) |
| { |
| va_list args; |
| va_start(args, aFormat); |
| otError status = RequestV(aCommand, aKey, aFormat, args); |
| va_end(args); |
| return status; |
| } |
| |
| 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; |
| } |
| |
| 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(aCommand, aKey, aFormat, aArgs); |
| mPropertyFormat = nullptr; |
| |
| return error; |
| } |
| |
| 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(aCommand, aKey, aFormat, aArgs); |
| mExpectedCommand = SPINEL_CMD_NOOP; |
| |
| return error; |
| } |
| |
| void RadioSpinel::HandleTransmitDone(uint32_t aCommand, |
| spinel_prop_key_t aKey, |
| const uint8_t *aBuffer, |
| uint16_t aLength) |
| { |
| otError error = OT_ERROR_NONE; |
| spinel_status_t status = SPINEL_STATUS_OK; |
| bool framePending = false; |
| bool headerUpdated = false; |
| spinel_ssize_t unpacked; |
| |
| VerifyOrExit(aCommand == SPINEL_CMD_PROP_VALUE_IS && aKey == SPINEL_PROP_LAST_STATUS, error = OT_ERROR_FAILED); |
| |
| unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_UINT_PACKED_S, &status); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| |
| aBuffer += unpacked; |
| aLength -= static_cast<uint16_t>(unpacked); |
| |
| unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_BOOL_S, &framePending); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| |
| aBuffer += unpacked; |
| aLength -= static_cast<uint16_t>(unpacked); |
| |
| unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_BOOL_S, &headerUpdated); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| |
| aBuffer += unpacked; |
| aLength -= static_cast<uint16_t>(unpacked); |
| |
| if (status == SPINEL_STATUS_OK) |
| { |
| SuccessOrExit(error = ParseRadioFrame(mAckRadioFrame, aBuffer, aLength, unpacked)); |
| aBuffer += unpacked; |
| aLength -= static_cast<uint16_t>(unpacked); |
| } |
| else |
| { |
| error = SpinelStatusToOtError(status); |
| } |
| |
| if ((sRadioCaps & OT_RADIO_CAPS_TRANSMIT_SEC) && (!mTransmitFrame->mInfo.mTxInfo.mIsHeaderUpdated) && headerUpdated) |
| { |
| Mac::TxFrame::ParseInfo frameInfo; |
| |
| IgnoreError(frameInfo.ParseFrom(*static_cast<Mac::TxFrame *>(mTransmitFrame), Mac::Frame::kParseFully)); |
| |
| if (frameInfo.mIsSecurityEnabled) |
| { |
| uint8_t keyIndex; |
| uint32_t frameCounter; |
| |
| // Replace transmit frame security key index and frame counter with the one filled by RCP |
| unpacked = spinel_datatype_unpack(aBuffer, aLength, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_UINT32_S, |
| &keyIndex, &frameCounter); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| |
| frameInfo.WriteKeyIndex(keyIndex); |
| frameInfo.WriteFrameCounter(frameCounter); |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| mMacFrameCounterSet = true; |
| #endif |
| } |
| } |
| |
| static_cast<Mac::TxFrame *>(mTransmitFrame)->SetIsHeaderUpdated(headerUpdated); |
| |
| exit: |
| // A parse error indicates an RCP misbehavior, so recover the RCP immediately. |
| mState = kStateTransmitDone; |
| if (error != OT_ERROR_PARSE) |
| { |
| mTxError = error; |
| } |
| else |
| { |
| mTxError = kErrorAbort; |
| HandleRcpTimeout(); |
| RecoverFromRcpFailure(); |
| } |
| UpdateParseErrorCount(error); |
| LogIfFail("Handle transmit done failed", error); |
| } |
| |
| otError RadioSpinel::Transmit(otRadioFrame &aFrame) |
| { |
| otError error = OT_ERROR_INVALID_STATE; |
| |
| VerifyOrExit(mState == kStateReceive || (mState == kStateSleep && (sRadioCaps & OT_RADIO_CAPS_SLEEP_TO_TX))); |
| |
| mTransmitFrame = &aFrame; |
| |
| #if OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_TIME_SYNC_ENABLE |
| if (mTransmitFrame->mInfo.mTxInfo.mIeInfo->mTimeIeOffset != 0) |
| { |
| otRadioTime64 netRadioTime = otPlatRadioGetNow(mInstance); |
| otRadioTime64 netSyncTime; |
| uint8_t *timeIe = mTransmitFrame->mPsdu + mTransmitFrame->mInfo.mTxInfo.mIeInfo->mTimeIeOffset; |
| |
| if (netRadioTime == UINT64_MAX) |
| { |
| // If we can't get the radio time, get the platform time |
| netSyncTime = static_cast<otRadioTime64>(static_cast<int64_t>(otPlatTimeGet()) + |
| mTransmitFrame->mInfo.mTxInfo.mIeInfo->mNetworkTimeOffset); |
| } |
| else |
| { |
| uint32_t transmitDelay = 0; |
| |
| // If supported, add a delay and transmit the network time at a precise moment |
| #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE |
| transmitDelay = kTxWaitUs / 10; |
| mTransmitFrame->mInfo.mTxInfo.mTxDelayBaseTime = static_cast<otRadioTime32>(netRadioTime); |
| mTransmitFrame->mInfo.mTxInfo.mTxDelay = transmitDelay; |
| #endif |
| netSyncTime = static_cast<otRadioTime64>(static_cast<int64_t>(netRadioTime) + transmitDelay + |
| mTransmitFrame->mInfo.mTxInfo.mIeInfo->mNetworkTimeOffset); |
| } |
| |
| *(timeIe++) = mTransmitFrame->mInfo.mTxInfo.mIeInfo->mTimeSyncSeq; |
| |
| for (uint8_t i = 0; i < sizeof(otRadioTime64); i++) |
| { |
| *(timeIe++) = static_cast<uint8_t>(netSyncTime & 0xff); |
| netSyncTime = netSyncTime >> 8; |
| } |
| } |
| #endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT && OPENTHREAD_CONFIG_TIME_SYNC_ENABLE |
| |
| // `otPlatRadioTxStarted()` is triggered immediately for now, which may be earlier than real started time. |
| if (mCallbacks.mTxStarted != nullptr) |
| { |
| mCallbacks.mTxStarted(mInstance, mTransmitFrame); |
| } |
| |
| error = Request(SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_STREAM_RAW, |
| SPINEL_DATATYPE_DATA_WLEN_S // Frame data |
| SPINEL_DATATYPE_UINT8_S // Channel |
| SPINEL_DATATYPE_UINT8_S // MaxCsmaBackoffs |
| SPINEL_DATATYPE_UINT8_S // MaxFrameRetries |
| SPINEL_DATATYPE_BOOL_S // CsmaCaEnabled |
| SPINEL_DATATYPE_BOOL_S // IsHeaderUpdated |
| SPINEL_DATATYPE_BOOL_S // IsARetx |
| SPINEL_DATATYPE_BOOL_S // IsSecurityProcessed |
| SPINEL_DATATYPE_UINT32_S // TxDelay |
| SPINEL_DATATYPE_UINT32_S // TxDelayBaseTime |
| SPINEL_DATATYPE_UINT8_S // RxChannelAfterTxDone |
| SPINEL_DATATYPE_INT8_S, // TxPower |
| mTransmitFrame->mPsdu, mTransmitFrame->mLength, mTransmitFrame->mChannel, |
| mTransmitFrame->mInfo.mTxInfo.mMaxCsmaBackoffs, mTransmitFrame->mInfo.mTxInfo.mMaxFrameRetries, |
| mTransmitFrame->mInfo.mTxInfo.mCsmaCaEnabled, mTransmitFrame->mInfo.mTxInfo.mIsHeaderUpdated, |
| mTransmitFrame->mInfo.mTxInfo.mIsARetx, mTransmitFrame->mInfo.mTxInfo.mIsSecurityProcessed, |
| mTransmitFrame->mInfo.mTxInfo.mTxDelay, mTransmitFrame->mInfo.mTxInfo.mTxDelayBaseTime, |
| mTransmitFrame->mInfo.mTxInfo.mRxChannelAfterTxDone, mTransmitFrame->mInfo.mTxInfo.mTxPower); |
| |
| if (error == OT_ERROR_NONE) |
| { |
| // Waiting for `TransmitDone` event. |
| mState = kStateTransmitting; |
| mTxRadioEndUs = otPlatTimeGet() + kTxWaitUs; |
| mChannel = mTransmitFrame->mChannel; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::Receive(uint8_t aChannel) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(mState != kStateDisabled, error = OT_ERROR_INVALID_STATE); |
| |
| if (mChannel != aChannel) |
| { |
| error = Set(SPINEL_PROP_PHY_CHAN, SPINEL_DATATYPE_UINT8_S, aChannel); |
| SuccessOrExit(error); |
| mChannel = aChannel; |
| } |
| |
| if (mState == kStateSleep) |
| { |
| error = Set(SPINEL_PROP_MAC_RAW_STREAM_ENABLED, SPINEL_DATATYPE_BOOL_S, true); |
| SuccessOrExit(error); |
| } |
| |
| if (mTxRadioTid != 0) |
| { |
| FreeTid(mTxRadioTid); |
| mTxRadioTid = 0; |
| } |
| |
| mState = kStateReceive; |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::ReceiveAt(uint64_t aWhen, uint32_t aDuration, uint8_t aChannel) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(mState != kStateDisabled, error = OT_ERROR_INVALID_STATE); |
| |
| error = Set(SPINEL_PROP_MAC_RX_AT, SPINEL_DATATYPE_UINT64_S SPINEL_DATATYPE_UINT32_S SPINEL_DATATYPE_UINT8_S, aWhen, |
| aDuration, aChannel); |
| SuccessOrExit(error); |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::Sleep(void) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| switch (mState) |
| { |
| case kStateReceive: |
| error = Set(SPINEL_PROP_MAC_RAW_STREAM_ENABLED, SPINEL_DATATYPE_BOOL_S, false); |
| SuccessOrExit(error); |
| |
| mState = kStateSleep; |
| break; |
| |
| case kStateSleep: |
| break; |
| |
| default: |
| error = OT_ERROR_INVALID_STATE; |
| break; |
| } |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::Enable(otInstance *aInstance) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(!IsEnabled()); |
| |
| mInstance = aInstance; |
| |
| SuccessOrExit(error = Set(SPINEL_PROP_PHY_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); |
| SuccessOrExit(error = Set(SPINEL_PROP_MAC_15_4_PANID, SPINEL_DATATYPE_UINT16_S, mPanId)); |
| SuccessOrExit(error = Set(SPINEL_PROP_MAC_15_4_SADDR, SPINEL_DATATYPE_UINT16_S, mShortAddress)); |
| SuccessOrExit(error = Get(SPINEL_PROP_PHY_RX_SENSITIVITY, SPINEL_DATATYPE_INT8_S, &mRxSensitivity)); |
| |
| mState = kStateSleep; |
| |
| exit: |
| if (error != OT_ERROR_NONE) |
| { |
| LogWarn("RadioSpinel enable: %s", otThreadErrorToString(error)); |
| error = OT_ERROR_FAILED; |
| } |
| |
| return error; |
| } |
| |
| otError RadioSpinel::Disable(void) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(IsEnabled()); |
| VerifyOrExit(mState == kStateSleep, error = OT_ERROR_INVALID_STATE); |
| |
| SuccessOrDie(Set(SPINEL_PROP_PHY_ENABLED, SPINEL_DATATYPE_BOOL_S, false)); |
| mState = kStateDisabled; |
| mInstance = nullptr; |
| |
| exit: |
| return error; |
| } |
| |
| #if OPENTHREAD_CONFIG_DIAG_ENABLE |
| void RadioSpinel::SetDiagOutputCallback(otPlatDiagOutputCallback aCallback, void *aContext) |
| { |
| mOutputCallback = aCallback; |
| mOutputContext = aContext; |
| } |
| |
| void RadioSpinel::GetDiagOutputCallback(otPlatDiagOutputCallback &aCallback, void *&aContext) |
| { |
| aCallback = mOutputCallback; |
| aContext = mOutputContext; |
| } |
| |
| otError RadioSpinel::RadioSpinelDiagProcess(char *aArgs[], uint8_t aArgsLength) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(aArgsLength > 1, error = OT_ERROR_INVALID_ARGS); |
| |
| aArgs++; |
| aArgsLength--; |
| |
| if (strcmp(aArgs[0], "buslatency") == 0) |
| { |
| if (aArgsLength == 1) |
| { |
| PlatDiagOutput("%lu\n", ToUlong(GetBusLatency())); |
| } |
| else if (aArgsLength == 2) |
| { |
| uint32_t busLatency; |
| char *endptr; |
| |
| busLatency = static_cast<uint32_t>(strtoul(aArgs[1], &endptr, 0)); |
| VerifyOrExit(*endptr == '\0', error = OT_ERROR_INVALID_ARGS); |
| |
| SetBusLatency(busLatency); |
| } |
| else |
| { |
| error = OT_ERROR_INVALID_ARGS; |
| } |
| } |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::PlatDiagProcess(const char *aString) |
| { |
| return Set(SPINEL_PROP_NEST_STREAM_MFG, SPINEL_DATATYPE_UTF8_S, aString); |
| } |
| |
| void RadioSpinel::PlatDiagOutput(const char *aFormat, ...) |
| { |
| va_list args; |
| |
| va_start(args, aFormat); |
| |
| if (mOutputCallback != nullptr) |
| { |
| mOutputCallback(aFormat, args, mOutputContext); |
| } |
| |
| va_end(args); |
| } |
| |
| #endif // OPENTHREAD_CONFIG_DIAG_ENABLE |
| |
| uint32_t RadioSpinel::GetRadioChannelMask(bool aPreferred) |
| { |
| uint8_t maskBuffer[kChannelMaskBufferSize]; |
| otError error = OT_ERROR_NONE; |
| uint32_t channelMask = 0; |
| const uint8_t *maskData = maskBuffer; |
| spinel_size_t maskLength = sizeof(maskBuffer); |
| |
| SuccessOrDie(Get(aPreferred ? SPINEL_PROP_PHY_CHAN_PREFERRED : SPINEL_PROP_PHY_CHAN_SUPPORTED, |
| SPINEL_DATATYPE_DATA_S, maskBuffer, &maskLength)); |
| |
| while (maskLength > 0) |
| { |
| uint8_t channel; |
| spinel_ssize_t unpacked; |
| |
| unpacked = spinel_datatype_unpack(maskData, maskLength, SPINEL_DATATYPE_UINT8_S, &channel); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_FAILED); |
| VerifyOrExit(channel < kChannelMaskBufferSize, error = OT_ERROR_PARSE); |
| channelMask |= (1UL << channel); |
| |
| maskData += unpacked; |
| maskLength -= static_cast<spinel_size_t>(unpacked); |
| } |
| |
| channelMask &= mMaxPowerTable.GetSupportedChannelMask(); |
| |
| exit: |
| UpdateParseErrorCount(error); |
| LogIfFail("Get radio channel mask failed", error); |
| return channelMask; |
| } |
| |
| otRadioState RadioSpinel::GetState(void) const |
| { |
| static const otRadioState sOtRadioStateMap[] = { |
| OT_RADIO_STATE_DISABLED, OT_RADIO_STATE_SLEEP, OT_RADIO_STATE_RECEIVE, |
| OT_RADIO_STATE_TRANSMIT, OT_RADIO_STATE_TRANSMIT, |
| }; |
| |
| return sOtRadioStateMap[mState]; |
| } |
| |
| void RadioSpinel::CalcRcpTimeOffset(void) |
| { |
| otError error = OT_ERROR_NONE; |
| uint64_t localTxTimestamp; |
| uint64_t localRxTimestamp; |
| uint64_t remoteTimestamp = 0; |
| uint8_t buffer[sizeof(remoteTimestamp)]; |
| spinel_ssize_t packed; |
| |
| /* |
| * Use a modified Network Time Protocol(NTP) to calculate the time offset |
| * Assume the time offset is D so that local can calculate remote time with, |
| * T' = T + D |
| * Where T is the local time and T' is the remote time. |
| * The time offset is calculated using timestamp measured at local and remote. |
| * |
| * T0 P P T2 |
| * local time --+----+----+---> |
| * \ | ^ |
| * get\ | /is |
| * v | / |
| * remote time -------+---------> |
| * T1' |
| * |
| * Based on the assumptions, |
| * 1. If the propagation time(P) from local to remote and from remote to local are same. |
| * 2. Both the host and RCP can accurately measure the time they send or receive a message. |
| * The degree to which these assumptions hold true determines the accuracy of the offset. |
| * Then, |
| * T1' = T0 + P + D and T1' = T2 - P + D |
| * Time offset can be calculated with, |
| * D = T1' - ((T0 + T2)/ 2) |
| */ |
| |
| VerifyOrExit(mTimeSyncOn); |
| VerifyOrExit(!mIsTimeSynced || (otPlatTimeGet() >= GetNextRadioTimeRecalcStart())); |
| |
| LogDebg("Trying to get RCP time offset"); |
| |
| packed = spinel_datatype_pack(buffer, sizeof(buffer), SPINEL_DATATYPE_UINT64_S, remoteTimestamp); |
| VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); |
| |
| localTxTimestamp = otPlatTimeGet(); |
| |
| // Dummy timestamp payload to make request length same as response |
| error = GetWithParam(SPINEL_PROP_RCP_TIMESTAMP, buffer, static_cast<spinel_size_t>(packed), |
| SPINEL_DATATYPE_UINT64_S, &remoteTimestamp); |
| |
| localRxTimestamp = otPlatTimeGet(); |
| |
| VerifyOrExit(error == OT_ERROR_NONE, mRadioTimeRecalcStart = localRxTimestamp); |
| |
| mRadioTimeOffset = (remoteTimestamp - ((localRxTimestamp / 2) + (localTxTimestamp / 2))); |
| mIsTimeSynced = true; |
| mRadioTimeRecalcStart = localRxTimestamp + OPENTHREAD_SPINEL_CONFIG_RCP_TIME_SYNC_INTERVAL; |
| |
| exit: |
| LogIfFail("Error calculating RCP time offset: %s", error); |
| } |
| |
| uint64_t RadioSpinel::GetNow(void) const { return (mIsTimeSynced) ? (otPlatTimeGet() + mRadioTimeOffset) : UINT64_MAX; } |
| |
| uint32_t RadioSpinel::GetBusSpeed(void) const { return GetSpinelDriver().GetSpinelInterface()->GetBusSpeed(); } |
| |
| uint32_t RadioSpinel::GetBusLatency(void) const { return mBusLatency; } |
| |
| void RadioSpinel::SetBusLatency(uint32_t aBusLatency) |
| { |
| mBusLatency = aBusLatency; |
| |
| if (IsEnabled() && mCallbacks.mBusLatencyChanged != nullptr) |
| { |
| mCallbacks.mBusLatencyChanged(mInstance); |
| } |
| } |
| |
| void RadioSpinel::HandleRcpUnexpectedReset(spinel_status_t aStatus) |
| { |
| OT_UNUSED_VARIABLE(aStatus); |
| |
| mMetrics.IncrementRcpUnexpectedResetCount(); |
| LogCrit("Unexpected RCP reset: %s", spinel_status_to_cstr(aStatus)); |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| mRcpFailure = kRcpFailureUnexpectedReset; |
| #else |
| if (sSupportsLogCrashDump) |
| { |
| mWaitingTid = 0; |
| LogDebg("RCP supports crash dump logging. Requesting crash dump."); |
| IgnoreReturnValue(Set(SPINEL_PROP_RCP_LOG_CRASH_DUMP, nullptr)); |
| } |
| |
| #if OPENTHREAD_SPINEL_CONFIG_ABORT_ON_UNEXPECTED_RCP_RESET_ENABLE |
| abort(); |
| #else |
| DieNow(OT_EXIT_RADIO_SPINEL_RESET); |
| #endif |
| #endif |
| } |
| |
| void RadioSpinel::HandleRcpTimeout(void) |
| { |
| mMetrics.IncrementRcpTimeoutCount(); |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| mRcpFailure = kRcpFailureTimeout; |
| #else |
| 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 |
| } |
| |
| 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; |
| bool skipReset = false; |
| |
| VerifyOrExit(mRcpRestorationEnabled); |
| |
| if (mRcpFailure == kRcpFailureNone) |
| { |
| ExitNow(); |
| } |
| |
| #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE |
| skipReset = (mRcpFailure == kRcpFailureUnexpectedReset); |
| #endif |
| |
| mRcpFailure = kRcpFailureNone; |
| |
| LogWarn("RCP failure detected"); |
| |
| mMetrics.IncrementRcpRestorationCount(); |
| ++mRcpFailureCount; |
| if (mRcpFailureCount > kMaxFailureCount) |
| { |
| LogCrit("Too many rcp failures, exiting"); |
| DieNow(OT_EXIT_FAILURE); |
| } |
| |
| if (sSupportsLogCrashDump) |
| { |
| mWaitingTid = 0; |
| LogDebg("RCP supports crash dump logging. Requesting crash dump."); |
| IgnoreReturnValue(Set(SPINEL_PROP_RCP_LOG_CRASH_DUMP, nullptr)); |
| mRcpFailure = kRcpFailureNone; |
| } |
| |
| LogWarn("Trying to recover (%d/%d)", mRcpFailureCount, kMaxFailureCount); |
| |
| mState = kStateDisabled; |
| |
| GetSpinelDriver().ClearRxBuffer(); |
| if (skipReset) |
| { |
| GetSpinelDriver().SetCoprocessorReady(); |
| } |
| else |
| { |
| GetSpinelDriver().ResetCoprocessor(mResetRadioOnStartup); |
| } |
| |
| mCmdTidsInUse = 0; |
| mCmdNextTid = 1; |
| mTxRadioTid = 0; |
| mWaitingTid = 0; |
| mError = OT_ERROR_NONE; |
| mIsTimeSynced = false; |
| |
| SuccessOrDie(Set(SPINEL_PROP_PHY_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); |
| mState = kStateSleep; |
| |
| RestoreProperties(); |
| |
| switch (recoveringState) |
| { |
| case kStateDisabled: |
| mState = kStateDisabled; |
| break; |
| case kStateSleep: |
| break; |
| case kStateReceive: |
| #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE |
| // In case multiple PANs are running, don't force RCP to receive state. |
| IgnoreReturnValue(Set(SPINEL_PROP_MAC_RAW_STREAM_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); |
| #else |
| SuccessOrDie(Set(SPINEL_PROP_MAC_RAW_STREAM_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); |
| #endif |
| mState = kStateReceive; |
| break; |
| case kStateTransmitting: |
| case kStateTransmitDone: |
| #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE |
| // In case multiple PANs are running, don't force RCP to receive state. |
| IgnoreReturnValue(Set(SPINEL_PROP_MAC_RAW_STREAM_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); |
| #else |
| SuccessOrDie(Set(SPINEL_PROP_MAC_RAW_STREAM_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); |
| #endif |
| mTxError = OT_ERROR_ABORT; |
| mState = kStateTransmitDone; |
| break; |
| } |
| |
| if (mEnergyScanning) |
| { |
| SuccessOrDie(EnergyScan(mScanChannel, mScanDuration)); |
| } |
| |
| --mRcpFailureCount; |
| |
| LogNote("RCP recovery is done"); |
| |
| exit: |
| return; |
| #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<RadioSpinel *>(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<RadioSpinel *>(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) |
| { |
| 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)); |
| #if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE |
| // In case multiple PANs are running, don't force RCP to change channel. |
| IgnoreReturnValue(Set(SPINEL_PROP_PHY_CHAN, SPINEL_DATATYPE_UINT8_S, mChannel)); |
| #else |
| SuccessOrDie(Set(SPINEL_PROP_PHY_CHAN, SPINEL_DATATYPE_UINT8_S, mChannel)); |
| #endif |
| |
| 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, mKeyIndex, mPrevKey.m8, sizeof(otMacKey), mCurrKey.m8, sizeof(otMacKey), |
| mNextKey.m8, sizeof(otMacKey))); |
| } |
| |
| if (mMacFrameCounterSet) |
| { |
| // There is a chance that radio/RCP has used some counters after otLinkGetFrameCounter() (for enh ack) and they |
| // are in queue to be sent to host (not yet processed by host RadioSpinel). Here we add some guard jump |
| // when we restore the frame counter. |
| // Consider the worst case: the radio/RCP continuously receives the shortest data frame and replies with the |
| // shortest enhanced ACK. The radio/RCP consumes at most 992 frame counters during the timeout time. |
| // The frame counter guard is set to 1000 which should ensure that the restored frame counter is unused. |
| // |
| // DataFrame: 6(PhyHeader) + 2(Fcf) + 1(Seq) + 6(AddrInfo) + 6(SecHeader) + 1(Payload) + 4(Mic) + 2(Fcs) = 28 |
| // AckFrame : 6(PhyHeader) + 2(Fcf) + 1(Seq) + 6(AddrInfo) + 6(SecHeader) + 2(Ie) + 4(Mic) + 2(Fcs) = 29 |
| // CounterGuard: 2000ms(Timeout) / [(28bytes(Data) + 29bytes(Ack)) * 32us/byte + 192us(Ifs)] = 992 |
| static constexpr uint16_t kFrameCounterGuard = 1000; |
| |
| SuccessOrDie(Set(SPINEL_PROP_RCP_MAC_FRAME_COUNTER, SPINEL_DATATYPE_UINT32_S, |
| otLinkGetFrameCounter(mInstance) + kFrameCounterGuard)); |
| } |
| |
| SuccessOrDie(Set(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, nullptr)); |
| |
| for (int i = 0; i < mSrcMatchShortEntryCount; ++i) |
| { |
| SuccessOrDie( |
| Insert(SPINEL_PROP_MAC_SRC_MATCH_SHORT_ADDRESSES, SPINEL_DATATYPE_UINT16_S, mSrcMatchShortEntries[i])); |
| } |
| |
| SuccessOrDie(Set(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, nullptr)); |
| |
| for (int i = 0; i < mSrcMatchExtEntryCount; ++i) |
| { |
| SuccessOrDie( |
| Insert(SPINEL_PROP_MAC_SRC_MATCH_EXTENDED_ADDRESSES, SPINEL_DATATYPE_EUI64_S, mSrcMatchExtEntries[i].m8)); |
| } |
| |
| if (mSrcMatchSet) |
| { |
| SuccessOrDie(Set(SPINEL_PROP_MAC_SRC_MATCH_ENABLED, SPINEL_DATATYPE_BOOL_S, mSrcMatchEnabled)); |
| } |
| |
| 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)); |
| } |
| |
| // Guarded, because the table holds `kPowerDefault` for every channel unless |
| // the radio URL configured one. Without this, every restore would issue 16 |
| // blocking transactions for users who never pass `max-power-table`. |
| if (mMaxPowerTableSet) |
| { |
| for (uint8_t channel = Radio::kChannelMin; channel <= Radio::kChannelMax; channel++) |
| { |
| int8_t power = mMaxPowerTable.GetTransmitPower(channel); |
| |
| if (power != OT_RADIO_POWER_INVALID) |
| { |
| otError error = |
| Set(SPINEL_PROP_PHY_CHAN_MAX_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT8_S, channel, power); |
| |
| // An RCP without `SPINEL_PROP_PHY_CHAN_MAX_POWER` answers with |
| // `SPINEL_STATUS_PROP_NOT_FOUND`, which maps to |
| // `OT_ERROR_NOT_IMPLEMENTED`, not the `OT_ERROR_NOT_FOUND` this |
| // used to tolerate. Treating it as fatal would turn an ordinary |
| // RCP reset into an exit. Warn once and stop: the remaining |
| // channels would answer the same way. |
| if (error == OT_ERROR_NOT_IMPLEMENTED || error == OT_ERROR_NOT_FOUND) |
| { |
| LogWarn("The RCP doesn't support setting the max transmit power"); |
| mMaxPowerTableSet = false; |
| break; |
| } |
| |
| if (error != OT_ERROR_NONE) |
| { |
| DieNow(OT_EXIT_FAILURE); |
| } |
| } |
| } |
| } |
| |
| if ((sRadioCaps & OT_RADIO_CAPS_RX_ON_WHEN_IDLE) != 0) |
| { |
| SuccessOrDie(Set(SPINEL_PROP_MAC_RX_ON_WHEN_IDLE_MODE, SPINEL_DATATYPE_BOOL_S, mRxOnWhenIdle)); |
| } |
| |
| #if OPENTHREAD_SPINEL_CONFIG_VENDOR_HOOK_ENABLE |
| if (mVendorRestorePropertiesCallback) |
| { |
| mVendorRestorePropertiesCallback(mVendorRestorePropertiesContext); |
| } |
| #endif |
| |
| if (mTimeSyncEnabled) |
| { |
| CalcRcpTimeOffset(); |
| } |
| } |
| #endif // OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| |
| otError RadioSpinel::GetMultipanActiveInterface(spinel_iid_t *aIid) |
| { |
| otError error = Get(SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE, SPINEL_DATATYPE_UINT8_S, aIid); |
| LogIfFail("Get GetMultipanActiveInterface failed", error); |
| return error; |
| } |
| |
| otError RadioSpinel::SetMultipanActiveInterface(spinel_iid_t aIid, bool aCompletePending) |
| { |
| otError error; |
| uint8_t value; |
| |
| VerifyOrExit(aIid == (aIid & SPINEL_MULTIPAN_INTERFACE_ID_MASK), error = OT_ERROR_INVALID_ARGS); |
| |
| value = static_cast<uint8_t>(aIid); |
| if (aCompletePending) |
| { |
| value |= (1 << SPINEL_MULTIPAN_INTERFACE_SOFT_SWITCH_SHIFT); |
| } |
| |
| error = Set(SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE, SPINEL_DATATYPE_UINT8_S, value); |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::SetChannelMaxTransmitPower(uint8_t aChannel, int8_t aMaxPower) |
| { |
| otError error = OT_ERROR_NONE; |
| VerifyOrExit(aChannel >= Radio::kChannelMin && aChannel <= Radio::kChannelMax, error = OT_ERROR_INVALID_ARGS); |
| mMaxPowerTable.SetTransmitPower(aChannel, aMaxPower); |
| SuccessOrExit(error = Set(SPINEL_PROP_PHY_CHAN_MAX_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT8_S, aChannel, |
| aMaxPower)); |
| |
| #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 |
| mMaxPowerTableSet = true; |
| #endif |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::SetRadioRegion(uint16_t aRegionCode) |
| { |
| otError error; |
| |
| error = Set(SPINEL_PROP_PHY_REGION_CODE, SPINEL_DATATYPE_UINT16_S, aRegionCode); |
| |
| if (error == OT_ERROR_NONE) |
| { |
| LogNote("Set region code \"%c%c\" successfully", static_cast<char>(aRegionCode >> 8), |
| static_cast<char>(aRegionCode)); |
| } |
| else |
| { |
| LogWarn("Failed to set region code \"%c%c\": %s", static_cast<char>(aRegionCode >> 8), |
| static_cast<char>(aRegionCode), otThreadErrorToString(error)); |
| } |
| |
| return error; |
| } |
| |
| otError RadioSpinel::GetRadioRegion(uint16_t *aRegionCode) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| VerifyOrExit(aRegionCode != nullptr, error = OT_ERROR_INVALID_ARGS); |
| error = Get(SPINEL_PROP_PHY_REGION_CODE, SPINEL_DATATYPE_UINT16_S, aRegionCode); |
| |
| exit: |
| return error; |
| } |
| |
| #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE |
| otError RadioSpinel::ConfigureEnhAckProbing(otLinkMetrics aLinkMetrics, |
| const otShortAddress &aShortAddress, |
| const otExtAddress &aExtAddress) |
| { |
| otError error = OT_ERROR_NONE; |
| uint8_t flags = 0; |
| |
| if (aLinkMetrics.mPduCount) |
| { |
| flags |= SPINEL_THREAD_LINK_METRIC_PDU_COUNT; |
| } |
| |
| if (aLinkMetrics.mLqi) |
| { |
| flags |= SPINEL_THREAD_LINK_METRIC_LQI; |
| } |
| |
| if (aLinkMetrics.mLinkMargin) |
| { |
| flags |= SPINEL_THREAD_LINK_METRIC_LINK_MARGIN; |
| } |
| |
| if (aLinkMetrics.mRssi) |
| { |
| flags |= SPINEL_THREAD_LINK_METRIC_RSSI; |
| } |
| |
| error = |
| Set(SPINEL_PROP_RCP_ENH_ACK_PROBING, SPINEL_DATATYPE_UINT16_S SPINEL_DATATYPE_EUI64_S SPINEL_DATATYPE_UINT8_S, |
| aShortAddress, aExtAddress.m8, flags); |
| |
| return error; |
| } |
| #endif |
| |
| #if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE |
| uint8_t RadioSpinel::GetCslAccuracy(void) |
| { |
| uint8_t accuracy = UINT8_MAX; |
| otError error = Get(SPINEL_PROP_RCP_CSL_ACCURACY, SPINEL_DATATYPE_UINT8_S, &accuracy); |
| |
| LogIfFail("Get CSL Accuracy failed", error); |
| return accuracy; |
| } |
| #endif |
| |
| #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE |
| uint8_t RadioSpinel::GetCslUncertainty(void) |
| { |
| uint8_t uncertainty = UINT8_MAX; |
| otError error = Get(SPINEL_PROP_RCP_CSL_UNCERTAINTY, SPINEL_DATATYPE_UINT8_S, &uncertainty); |
| |
| LogIfFail("Get CSL Uncertainty failed", error); |
| return uncertainty; |
| } |
| #endif |
| |
| #if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE |
| otError RadioSpinel::AddCalibratedPower(uint8_t aChannel, |
| int16_t aActualPower, |
| const uint8_t *aRawPowerSetting, |
| uint16_t aRawPowerSettingLength) |
| { |
| otError error; |
| |
| assert(aRawPowerSetting != nullptr); |
| SuccessOrExit(error = Insert(SPINEL_PROP_PHY_CALIBRATED_POWER, |
| SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT16_S SPINEL_DATATYPE_DATA_WLEN_S, aChannel, |
| aActualPower, aRawPowerSetting, aRawPowerSettingLength)); |
| |
| exit: |
| return error; |
| } |
| |
| otError RadioSpinel::ClearCalibratedPowers(void) { return Set(SPINEL_PROP_PHY_CALIBRATED_POWER, nullptr); } |
| |
| otError RadioSpinel::SetChannelTargetPower(uint8_t aChannel, int16_t aTargetPower) |
| { |
| otError error = OT_ERROR_NONE; |
| VerifyOrExit(aChannel >= Radio::kChannelMin && aChannel <= Radio::kChannelMax, error = OT_ERROR_INVALID_ARGS); |
| error = |
| Set(SPINEL_PROP_PHY_CHAN_TARGET_POWER, SPINEL_DATATYPE_UINT8_S SPINEL_DATATYPE_INT16_S, aChannel, aTargetPower); |
| |
| exit: |
| return error; |
| } |
| #endif // OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE |
| |
| #if OPENTHREAD_SPINEL_CONFIG_COMPATIBILITY_ERROR_CALLBACK_ENABLE |
| void RadioSpinel::SetCompatibilityErrorCallback(otRadioSpinelCompatibilityErrorCallback aCallback, void *aContext) |
| { |
| mCompatibilityErrorCallback = aCallback; |
| mCompatibilityErrorContext = aContext; |
| } |
| #endif |
| |
| void RadioSpinel::HandleCompatibilityError(void) |
| { |
| #if OPENTHREAD_SPINEL_CONFIG_COMPATIBILITY_ERROR_CALLBACK_ENABLE |
| if (mCompatibilityErrorCallback) |
| { |
| mCompatibilityErrorCallback(mCompatibilityErrorContext); |
| } |
| #endif |
| DieNow(OT_EXIT_RADIO_SPINEL_INCOMPATIBLE); |
| } |
| |
| void RadioSpinel::MetricsTracker::Init(void) { RestoreMetrics(); } |
| |
| void RadioSpinel::MetricsTracker::RestoreMetrics(void) |
| { |
| VerifyOrExit((mCallbacks.mSaveRadioSpinelMetrics != nullptr) && (mCallbacks.mRestoreRadioSpinelMetrics != nullptr)); |
| |
| if (mCallbacks.mRestoreRadioSpinelMetrics(mMetrics, mCallbacks.mRadioSpinelMetricsContext) != OT_ERROR_NONE) |
| { |
| memset(&mMetrics, 0, sizeof(mMetrics)); |
| mCallbacks.mSaveRadioSpinelMetrics(mMetrics, mCallbacks.mRadioSpinelMetricsContext); |
| } |
| |
| exit: |
| return; |
| } |
| |
| void RadioSpinel::MetricsTracker::SaveMetrics(void) |
| { |
| VerifyOrExit(mCallbacks.mSaveRadioSpinelMetrics != nullptr); |
| mCallbacks.mSaveRadioSpinelMetrics(mMetrics, mCallbacks.mRadioSpinelMetricsContext); |
| |
| exit: |
| return; |
| } |
| |
| void RadioSpinel::MetricsTracker::IncrementCount(MetricType aType) |
| { |
| RestoreMetrics(); |
| |
| switch (aType) |
| { |
| case kTypeTimeoutCount: |
| mMetrics.mRcpTimeoutCount++; |
| break; |
| |
| case kTypeUnexpectResetCount: |
| mMetrics.mRcpUnexpectedResetCount++; |
| break; |
| |
| case kTypeRestorationCount: |
| mMetrics.mRcpRestorationCount++; |
| break; |
| |
| case kTypeSpinelParseErrorCount: |
| mMetrics.mSpinelParseErrorCount++; |
| break; |
| |
| default: |
| OT_ASSERT(false); |
| break; |
| } |
| |
| SaveMetrics(); |
| } |
| |
| } // namespace Spinel |
| } // namespace ot |