| /* |
| * Copyright (c) 2024, The OpenThread Authors. |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of the copyright holder nor the |
| * names of its contributors may be used to endorse or promote products |
| * derived from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE |
| * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
| * POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "spinel_driver.hpp" |
| |
| #include <assert.h> |
| |
| #include <openthread/platform/time.h> |
| |
| #include "common/code_utils.hpp" |
| #include "common/new.hpp" |
| #include "lib/platform/exit_code.h" |
| #include "lib/spinel/spinel.h" |
| #include "lib/utils/math.hpp" |
| |
| namespace ot { |
| namespace Spinel { |
| |
| constexpr spinel_tid_t sTid = 1; ///< In Spinel Driver, only use Tid as 1. |
| |
| SpinelDriver::SpinelDriver(void) |
| : Logger("SpinelDriver") |
| , mSpinelInterface(nullptr) |
| , mWaitingKey(SPINEL_PROP_LAST_STATUS) |
| , mIsWaitingForResponse(false) |
| , mIid(SPINEL_HEADER_INVALID_IID) |
| , mSpinelVersionMajor(-1) |
| , mSpinelVersionMinor(-1) |
| , mIsCoprocessorReady(false) |
| #if OPENTHREAD_SPINEL_CONFIG_COPROCESSOR_RESET_FAILURE_CALLBACK_ENABLE |
| , mCoprocessorResetFailureCallback(nullptr) |
| , mCoprocessorResetFailureContext(nullptr) |
| #endif |
| { |
| memset(mVersion, 0, sizeof(mVersion)); |
| |
| mReceivedFrameHandler = &HandleInitialFrame; |
| mFrameHandlerContext = this; |
| } |
| |
| CoprocessorType SpinelDriver::Init(SpinelInterface &aSpinelInterface, |
| bool aSoftwareReset, |
| const spinel_iid_t *aIidList, |
| uint8_t aIidListLength) |
| { |
| CoprocessorType coprocessorType; |
| |
| mSpinelInterface = &aSpinelInterface; |
| mRxFrameBuffer.Clear(); |
| SuccessOrDie(mSpinelInterface->Init(HandleReceivedFrame, this, mRxFrameBuffer)); |
| |
| VerifyOrDie(aIidList != nullptr, OT_EXIT_INVALID_ARGUMENTS); |
| VerifyOrDie(aIidListLength != 0 && aIidListLength <= mIidList.GetMaxSize(), OT_EXIT_INVALID_ARGUMENTS); |
| |
| for (uint8_t i = 0; i < aIidListLength; i++) |
| { |
| SuccessOrDie(mIidList.PushBack(aIidList[i])); |
| } |
| mIid = aIidList[0]; |
| |
| ResetCoprocessor(aSoftwareReset); |
| SuccessOrDie(CheckSpinelVersion()); |
| SuccessOrDie(GetCoprocessorVersion()); |
| SuccessOrDie(GetCoprocessorCaps()); |
| |
| coprocessorType = GetCoprocessorType(); |
| if (coprocessorType == OT_COPROCESSOR_UNKNOWN) |
| { |
| LogCrit("The coprocessor mode is unknown!"); |
| DieNow(OT_EXIT_FAILURE); |
| } |
| |
| return coprocessorType; |
| } |
| |
| void SpinelDriver::Deinit(void) |
| { |
| // This allows implementing pseudo reset. |
| new (this) SpinelDriver(); |
| } |
| |
| otError SpinelDriver::SendReset(uint8_t aResetType) |
| { |
| otError error = OT_ERROR_NONE; |
| uint8_t buffer[kMaxSpinelFrame]; |
| spinel_ssize_t packed; |
| |
| // Pack the header, command and key |
| packed = spinel_datatype_pack(buffer, sizeof(buffer), SPINEL_DATATYPE_COMMAND_S SPINEL_DATATYPE_UINT8_S, |
| SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid), SPINEL_CMD_RESET, aResetType); |
| |
| VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); |
| |
| SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, static_cast<uint16_t>(packed))); |
| LogSpinelFrame(buffer, static_cast<uint16_t>(packed), true /* aTx */); |
| |
| exit: |
| return error; |
| } |
| |
| #if OPENTHREAD_SPINEL_CONFIG_COPROCESSOR_RESET_FAILURE_CALLBACK_ENABLE |
| void SpinelDriver::SetCoprocessorResetFailureCallback(otSpinelDriverCoprocessorResetFailureCallback aCallback, |
| void *aContext) |
| { |
| mCoprocessorResetFailureCallback = aCallback; |
| mCoprocessorResetFailureContext = aContext; |
| } |
| #endif |
| |
| void SpinelDriver::ResetCoprocessor(bool aSoftwareReset) |
| { |
| bool hardwareReset; |
| bool resetDone = false; |
| |
| #if OPENTHREAD_SPINEL_CONFIG_SKIP_RESET_IF_READY |
| VerifyOrExit(!mIsCoprocessorReady, resetDone = true); |
| #endif |
| |
| // Before resetting the co-processor, mIsCoprocessorReady must be set to false. |
| // Otherwise, in some cases, the host may misinterpret the reset result. |
| // For example, if mIsCoprocessorReady is true when calling ResetCoprocessor, |
| // and the reset fails but the co-processor sends some invalid data, `WaitResponse` |
| // may mistakenly return OT_ERROR_NONE, causing the host to believe the reset succeeded. |
| mIsCoprocessorReady = false; |
| mWaitingKey = SPINEL_PROP_LAST_STATUS; |
| |
| if (aSoftwareReset && (SendReset(SPINEL_RESET_STACK) == OT_ERROR_NONE) && (WaitResponse() == OT_ERROR_NONE)) |
| { |
| VerifyOrExit(mIsCoprocessorReady, resetDone = false); |
| LogInfo("Software reset co-processor successfully"); |
| ExitNow(resetDone = true); |
| } |
| |
| hardwareReset = (mSpinelInterface->HardwareReset() == OT_ERROR_NONE); |
| |
| if (hardwareReset) |
| { |
| SuccessOrExit(WaitResponse()); |
| } |
| |
| resetDone = true; |
| |
| if (hardwareReset) |
| { |
| LogInfo("Hardware reset co-processor successfully"); |
| } |
| else |
| { |
| LogInfo("co-processor self reset successfully"); |
| } |
| |
| exit: |
| if (!resetDone) |
| { |
| LogCrit("Failed to reset co-processor!"); |
| #if OPENTHREAD_SPINEL_CONFIG_COPROCESSOR_RESET_FAILURE_CALLBACK_ENABLE |
| if (mCoprocessorResetFailureCallback) |
| { |
| mCoprocessorResetFailureCallback(mCoprocessorResetFailureContext); |
| } |
| #endif |
| DieNow(OT_EXIT_FAILURE); |
| } |
| } |
| |
| void SpinelDriver::Process(const void *aContext) |
| { |
| if (mRxFrameBuffer.HasSavedFrame()) |
| { |
| ProcessFrameQueue(); |
| } |
| |
| mSpinelInterface->Process(aContext); |
| |
| if (mRxFrameBuffer.HasSavedFrame()) |
| { |
| ProcessFrameQueue(); |
| } |
| } |
| |
| otError SpinelDriver::SendCommand(uint32_t aCommand, spinel_prop_key_t aKey, spinel_tid_t aTid) |
| { |
| otError error = OT_ERROR_NONE; |
| uint8_t buffer[kMaxSpinelFrame]; |
| spinel_ssize_t packed; |
| uint16_t offset; |
| |
| // Pack the header, command and key |
| packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | aTid, |
| aCommand, aKey); |
| |
| VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); |
| |
| offset = static_cast<uint16_t>(packed); |
| |
| SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, offset)); |
| LogSpinelFrame(buffer, offset, true /* aTx */); |
| |
| exit: |
| return error; |
| } |
| |
| otError SpinelDriver::SendCommand(uint32_t aCommand, |
| spinel_prop_key_t aKey, |
| spinel_tid_t aTid, |
| const char *aFormat, |
| va_list aArgs) |
| { |
| otError error = OT_ERROR_NONE; |
| uint8_t buffer[kMaxSpinelFrame]; |
| spinel_ssize_t packed; |
| uint16_t offset; |
| |
| // Pack the header, command and key |
| packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | aTid, |
| aCommand, aKey); |
| |
| VerifyOrExit(packed > 0 && static_cast<size_t>(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); |
| |
| offset = static_cast<uint16_t>(packed); |
| |
| // Pack the data (if any) |
| if (aFormat) |
| { |
| packed = spinel_datatype_vpack(buffer + offset, sizeof(buffer) - offset, aFormat, aArgs); |
| VerifyOrExit(packed > 0 && static_cast<size_t>(packed + offset) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); |
| |
| offset += static_cast<uint16_t>(packed); |
| } |
| |
| SuccessOrExit(error = mSpinelInterface->SendFrame(buffer, offset)); |
| LogSpinelFrame(buffer, offset, true /* aTx */); |
| |
| exit: |
| return error; |
| } |
| |
| void SpinelDriver::SetFrameHandler(ReceivedFrameHandler aReceivedFrameHandler, |
| SavedFrameHandler aSavedFrameHandler, |
| void *aContext) |
| { |
| mReceivedFrameHandler = aReceivedFrameHandler; |
| mSavedFrameHandler = aSavedFrameHandler; |
| mFrameHandlerContext = aContext; |
| } |
| |
| otError SpinelDriver::WaitResponse(void) |
| { |
| otError error = OT_ERROR_NONE; |
| uint64_t end = otPlatTimeGet() + kMaxWaitTime * kUsPerMs; |
| |
| LogDebg("Waiting response: key=%lu", Lib::Utils::ToUlong(mWaitingKey)); |
| |
| do |
| { |
| uint64_t now = otPlatTimeGet(); |
| |
| if ((end <= now) || (mSpinelInterface->WaitForFrame(end - now) != OT_ERROR_NONE)) |
| { |
| LogWarn("Wait for response timeout"); |
| ExitNow(error = OT_ERROR_RESPONSE_TIMEOUT); |
| } |
| } while (mIsWaitingForResponse || !mIsCoprocessorReady); |
| |
| mWaitingKey = SPINEL_PROP_LAST_STATUS; |
| |
| exit: |
| return error; |
| } |
| |
| void SpinelDriver::HandleReceivedFrame(void *aContext) { static_cast<SpinelDriver *>(aContext)->HandleReceivedFrame(); } |
| |
| void SpinelDriver::HandleReceivedFrame(void) |
| { |
| otError error = OT_ERROR_NONE; |
| uint8_t header; |
| spinel_ssize_t unpacked; |
| bool shouldSave = true; |
| spinel_iid_t iid; |
| |
| LogSpinelFrame(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), false); |
| unpacked = spinel_datatype_unpack(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), "C", &header); |
| |
| // Accept spinel messages with the correct IID or broadcast IID. |
| iid = SPINEL_HEADER_GET_IID(header); |
| |
| if (!mIidList.Contains(iid)) |
| { |
| mRxFrameBuffer.DiscardFrame(); |
| ExitNow(); |
| } |
| |
| VerifyOrExit(unpacked > 0 && (header & SPINEL_HEADER_FLAG) == SPINEL_HEADER_FLAG, error = OT_ERROR_PARSE); |
| |
| assert(mReceivedFrameHandler != nullptr && mFrameHandlerContext != nullptr); |
| mReceivedFrameHandler(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), header, shouldSave, |
| mFrameHandlerContext); |
| |
| if (shouldSave) |
| { |
| error = mRxFrameBuffer.SaveFrame(); |
| } |
| else |
| { |
| mRxFrameBuffer.DiscardFrame(); |
| } |
| |
| exit: |
| if (error != OT_ERROR_NONE) |
| { |
| mRxFrameBuffer.DiscardFrame(); |
| LogWarn("Error handling spinel frame: %s", otThreadErrorToString(error)); |
| } |
| } |
| |
| void SpinelDriver::HandleInitialFrame(const uint8_t *aFrame, |
| uint16_t aLength, |
| uint8_t aHeader, |
| bool &aSave, |
| void *aContext) |
| { |
| static_cast<SpinelDriver *>(aContext)->HandleInitialFrame(aFrame, aLength, aHeader, aSave); |
| } |
| |
| void SpinelDriver::HandleInitialFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aSave) |
| { |
| spinel_prop_key_t key; |
| uint8_t *data = nullptr; |
| spinel_size_t len = 0; |
| uint8_t header = 0; |
| uint32_t cmd = 0; |
| spinel_ssize_t rval = 0; |
| spinel_ssize_t unpacked; |
| otError error = OT_ERROR_NONE; |
| |
| OT_UNUSED_VARIABLE(aHeader); |
| |
| rval = spinel_datatype_unpack(aFrame, aLength, "CiiD", &header, &cmd, &key, &data, &len); |
| VerifyOrExit(rval > 0 && cmd >= SPINEL_CMD_PROP_VALUE_IS && cmd <= SPINEL_CMD_PROP_VALUE_REMOVED, |
| error = OT_ERROR_PARSE); |
| |
| VerifyOrExit(cmd == SPINEL_CMD_PROP_VALUE_IS, error = OT_ERROR_DROP); |
| |
| if (key == SPINEL_PROP_LAST_STATUS) |
| { |
| spinel_status_t status = SPINEL_STATUS_OK; |
| |
| unpacked = spinel_datatype_unpack(data, len, "i", &status); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| |
| if (status >= SPINEL_STATUS_RESET__BEGIN && status <= SPINEL_STATUS_RESET__END) |
| { |
| // this clear is necessary in case the RCP has sent messages between disable and reset |
| mRxFrameBuffer.Clear(); |
| |
| LogInfo("co-processor reset: %s", spinel_status_to_cstr(status)); |
| mIsCoprocessorReady = true; |
| } |
| else |
| { |
| LogInfo("co-processor last status: %s", spinel_status_to_cstr(status)); |
| ExitNow(); |
| } |
| } |
| else |
| { |
| // Drop other frames when the key isn't waiting key. |
| VerifyOrExit(mWaitingKey == key, error = OT_ERROR_DROP); |
| |
| if (key == SPINEL_PROP_PROTOCOL_VERSION) |
| { |
| unpacked = spinel_datatype_unpack(data, len, (SPINEL_DATATYPE_UINT_PACKED_S SPINEL_DATATYPE_UINT_PACKED_S), |
| &mSpinelVersionMajor, &mSpinelVersionMinor); |
| |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| } |
| else if (key == SPINEL_PROP_NCP_VERSION) |
| { |
| unpacked = spinel_datatype_unpack_in_place(data, len, SPINEL_DATATYPE_UTF8_S, mVersion, sizeof(mVersion)); |
| |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| } |
| else if (key == SPINEL_PROP_CAPS) |
| { |
| uint8_t capsBuffer[kCapsBufferSize]; |
| spinel_size_t capsLength = sizeof(capsBuffer); |
| const uint8_t *capsData = capsBuffer; |
| |
| unpacked = spinel_datatype_unpack_in_place(data, len, SPINEL_DATATYPE_DATA_S, capsBuffer, &capsLength); |
| |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| |
| while (capsLength > 0) |
| { |
| unsigned int capability; |
| |
| unpacked = spinel_datatype_unpack(capsData, capsLength, SPINEL_DATATYPE_UINT_PACKED_S, &capability); |
| VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); |
| |
| SuccessOrExit(error = mCoprocessorCaps.PushBack(capability)); |
| |
| capsData += unpacked; |
| capsLength -= static_cast<spinel_size_t>(unpacked); |
| } |
| } |
| |
| mIsWaitingForResponse = false; |
| } |
| |
| exit: |
| aSave = false; |
| LogIfFail("Error processing frame", error); |
| } |
| |
| otError SpinelDriver::CheckSpinelVersion(void) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_PROTOCOL_VERSION, sTid)); |
| mIsWaitingForResponse = true; |
| mWaitingKey = SPINEL_PROP_PROTOCOL_VERSION; |
| |
| SuccessOrExit(error = WaitResponse()); |
| |
| if ((mSpinelVersionMajor != SPINEL_PROTOCOL_VERSION_THREAD_MAJOR) || |
| (mSpinelVersionMinor != SPINEL_PROTOCOL_VERSION_THREAD_MINOR)) |
| { |
| LogCrit("Spinel version mismatch - Posix:%d.%d, co-processor:%d.%d", SPINEL_PROTOCOL_VERSION_THREAD_MAJOR, |
| SPINEL_PROTOCOL_VERSION_THREAD_MINOR, mSpinelVersionMajor, mSpinelVersionMinor); |
| DieNow(OT_EXIT_RADIO_SPINEL_INCOMPATIBLE); |
| } |
| |
| exit: |
| return error; |
| } |
| |
| otError SpinelDriver::GetCoprocessorVersion(void) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_NCP_VERSION, sTid)); |
| mIsWaitingForResponse = true; |
| mWaitingKey = SPINEL_PROP_NCP_VERSION; |
| |
| SuccessOrExit(error = WaitResponse()); |
| exit: |
| return error; |
| } |
| |
| otError SpinelDriver::GetCoprocessorCaps(void) |
| { |
| otError error = OT_ERROR_NONE; |
| |
| SuccessOrExit(error = SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_CAPS, sTid)); |
| mIsWaitingForResponse = true; |
| mWaitingKey = SPINEL_PROP_CAPS; |
| |
| SuccessOrExit(error = WaitResponse()); |
| exit: |
| return error; |
| } |
| |
| CoprocessorType SpinelDriver::GetCoprocessorType(void) |
| { |
| CoprocessorType type = OT_COPROCESSOR_UNKNOWN; |
| |
| if (CoprocessorHasCap(SPINEL_CAP_CONFIG_RADIO)) |
| { |
| type = OT_COPROCESSOR_RCP; |
| } |
| else if (CoprocessorHasCap(SPINEL_CAP_CONFIG_FTD) || CoprocessorHasCap(SPINEL_CAP_CONFIG_MTD)) |
| { |
| type = OT_COPROCESSOR_NCP; |
| } |
| |
| return type; |
| } |
| |
| void SpinelDriver::ProcessFrameQueue(void) |
| { |
| uint8_t *frame = nullptr; |
| uint16_t length; |
| |
| assert(mSavedFrameHandler != nullptr && mFrameHandlerContext != nullptr); |
| |
| while (mRxFrameBuffer.GetNextSavedFrame(frame, length) == OT_ERROR_NONE) |
| { |
| mSavedFrameHandler(frame, length, mFrameHandlerContext); |
| } |
| |
| mRxFrameBuffer.ClearSavedFrames(); |
| } |
| |
| } // namespace Spinel |
| } // namespace ot |