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