[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:
Li Cao
2024-04-17 12:55:35 -07:00
committed by GitHub
parent a234addb13
commit cf58985747
6 changed files with 887 additions and 357 deletions
+2
View File
@@ -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",
+1
View File
@@ -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
View File
@@ -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)
{
+29 -67
View File
@@ -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
+466
View File
@@ -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
+306
View File
@@ -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_