blob: 31c8a96721d53ecf248660c946f4c4a6a8afb3bd [file] [edit]
/*
* 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