Files
openthread/src/ncp/ncp_base.cpp
T
Marek Porwisz 834c8cbc8e [spinel] add support for multiple spinel interfaces (#9360)
This feature allows the RCP to support multiple host stacks on different PANs
by making use of the spinel Interface ID.

Created unit tests for testing multipan feature with multiple ot-instance
support.

Based on Si-Labs PR #8914 by @parag-silabs, but a little different approach.
Instead of handling everything by a single sub-mac instance, multiple
OpenThread instances are created on RCP side that map to different IID.
Thanks to this there are separate data kept for each interface. Platform
is able to determine interface by ot instance pointer passed as an argument
to most of the API functions.
Tx/scan queue was removed as it is possible to request transmission in
parallel, it is up to the platform to decide if it should fail or queue
second tx or it has two radios available.

NOTE:
Platform needs to provide different otRadioFrame of each instance and
the processing needs to take into account the instance being used.

Signed-off-by: Marek Porwisz <marek.porwisz@nordicsemi.no>
2023-11-28 16:16:37 -08:00

2769 lines
81 KiB
C++

/*
* Copyright (c) 2016-2017, The OpenThread Authors.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the copyright holder nor the
* names of its contributors may be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* @file
* This file implements general thread device required Spinel interface to the OpenThread stack.
*/
#include "ncp_base.hpp"
#include <stdarg.h>
#include <stdlib.h>
#include <openthread/diag.h>
#include <openthread/icmp6.h>
#include <openthread/link.h>
#include <openthread/logging.h>
#include <openthread/ncp.h>
#include <openthread/network_time.h>
#include <openthread/platform/misc.h>
#include <openthread/platform/radio.h>
#include "common/code_utils.hpp"
#include "common/debug.hpp"
#include "radio/radio.hpp"
namespace ot {
namespace Ncp {
// ----------------------------------------------------------------------------
// MARK: Utility Functions
// ----------------------------------------------------------------------------
uint8_t NcpBase::InstanceToIid(Instance *aInstance)
{
uint8_t index = 0;
OT_UNUSED_VARIABLE(aInstance);
#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO
index = SPINEL_HEADER_GET_IID(SPINEL_HEADER_IID_BROADCAST); // use broadcast if no match
for (int i = 0; i < kSpinelInterfaceCount; i++)
{
if (aInstance == mInstances[i])
{
index = i;
break;
}
}
#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO
return index;
}
Instance *NcpBase::IidToInstance(uint8_t aIid)
{
Instance *instance;
OT_ASSERT(aIid < kSpinelInterfaceCount);
#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO
instance = mInstances[aIid];
#else
OT_UNUSED_VARIABLE(aIid);
instance = mInstance;
#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO
return instance;
}
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
static bool HasOnly1BitSet(uint32_t aValue) { return aValue != 0 && ((aValue & (aValue - 1)) == 0); }
static uint8_t IndexOfMSB(uint32_t aValue)
{
uint8_t index = 0;
while (aValue >>= 1)
{
index++;
}
return index;
}
#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
spinel_status_t NcpBase::ThreadErrorToSpinelStatus(otError aError)
{
spinel_status_t ret;
switch (aError)
{
case OT_ERROR_NONE:
ret = SPINEL_STATUS_OK;
break;
case OT_ERROR_FAILED:
ret = SPINEL_STATUS_FAILURE;
break;
case OT_ERROR_DROP:
ret = SPINEL_STATUS_DROPPED;
break;
case OT_ERROR_NO_BUFS:
ret = SPINEL_STATUS_NOMEM;
break;
case OT_ERROR_BUSY:
ret = SPINEL_STATUS_BUSY;
break;
case OT_ERROR_PARSE:
ret = SPINEL_STATUS_PARSE_ERROR;
break;
case OT_ERROR_INVALID_ARGS:
ret = SPINEL_STATUS_INVALID_ARGUMENT;
break;
case OT_ERROR_NOT_IMPLEMENTED:
ret = SPINEL_STATUS_UNIMPLEMENTED;
break;
case OT_ERROR_INVALID_STATE:
ret = SPINEL_STATUS_INVALID_STATE;
break;
case OT_ERROR_NO_ACK:
ret = SPINEL_STATUS_NO_ACK;
break;
case OT_ERROR_CHANNEL_ACCESS_FAILURE:
ret = SPINEL_STATUS_CCA_FAILURE;
break;
case OT_ERROR_ALREADY:
ret = SPINEL_STATUS_ALREADY;
break;
case OT_ERROR_NOT_FOUND:
ret = SPINEL_STATUS_ITEM_NOT_FOUND;
break;
case OT_ERROR_UNKNOWN_NEIGHBOR:
ret = SPINEL_STATUS_UNKNOWN_NEIGHBOR;
break;
case OT_ERROR_NOT_CAPABLE:
ret = SPINEL_STATUS_NOT_CAPABLE;
break;
case OT_ERROR_RESPONSE_TIMEOUT:
ret = SPINEL_STATUS_RESPONSE_TIMEOUT;
break;
default:
// Unknown error code. Wrap it as a Spinel status and return that.
ret = static_cast<spinel_status_t>(SPINEL_STATUS_STACK_NATIVE__BEGIN + static_cast<uint32_t>(aError));
break;
}
return ret;
}
static spinel_status_t ResetReasonToSpinelStatus(otPlatResetReason aReason)
{
spinel_status_t ret;
switch (aReason)
{
case OT_PLAT_RESET_REASON_POWER_ON:
ret = SPINEL_STATUS_RESET_POWER_ON;
break;
case OT_PLAT_RESET_REASON_EXTERNAL:
ret = SPINEL_STATUS_RESET_EXTERNAL;
break;
case OT_PLAT_RESET_REASON_SOFTWARE:
ret = SPINEL_STATUS_RESET_SOFTWARE;
break;
case OT_PLAT_RESET_REASON_FAULT:
ret = SPINEL_STATUS_RESET_FAULT;
break;
case OT_PLAT_RESET_REASON_CRASH:
ret = SPINEL_STATUS_RESET_CRASH;
break;
case OT_PLAT_RESET_REASON_ASSERT:
ret = SPINEL_STATUS_RESET_ASSERT;
break;
case OT_PLAT_RESET_REASON_WATCHDOG:
ret = SPINEL_STATUS_RESET_WATCHDOG;
break;
case OT_PLAT_RESET_REASON_OTHER:
ret = SPINEL_STATUS_RESET_OTHER;
break;
default:
ret = SPINEL_STATUS_RESET_UNKNOWN;
break;
}
return ret;
}
// ----------------------------------------------------------------------------
// MARK: Class Boilerplate
// ----------------------------------------------------------------------------
NcpBase *NcpBase::sNcpInstance = nullptr;
#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO
NcpBase::NcpBase(Instance **aInstances, uint8_t aCount)
: NcpBase(aInstances[0])
{
OT_ASSERT(aCount > 0);
OT_ASSERT(aCount < SPINEL_HEADER_IID_MAX); // One IID reserved for broadcast
uint8_t skipped = 0;
for (int i = 0; i < aCount; i++)
{
if ((i + skipped) == SPINEL_HEADER_GET_IID(SPINEL_HEADER_IID_BROADCAST))
{
mInstances[i + skipped] = nullptr;
skipped++;
}
OT_ASSERT(i + skipped <= SPINEL_HEADER_IID_MAX);
mInstances[i + skipped] = aInstances[i];
}
}
#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO
NcpBase::NcpBase(Instance *aInstance)
: mInstance(aInstance)
, mTxFrameBuffer(mTxBuffer, sizeof(mTxBuffer))
, mEncoder(mTxFrameBuffer)
, mHostPowerStateInProgress(false)
, mLastStatus(SPINEL_STATUS_OK)
, mScanChannelMask(Radio::kSupportedChannels)
, mScanPeriod(200)
, mDiscoveryScanJoinerFlag(false)
, mDiscoveryScanEnableFiltering(false)
, mDiscoveryScanPanId(0xffff)
, mUpdateChangedPropsTask(*aInstance, NcpBase::UpdateChangedProps)
, mThreadChangedFlags(0)
, mHostPowerState(SPINEL_HOST_POWER_STATE_ONLINE)
, mHostPowerReplyFrameTag(Spinel::Buffer::kInvalidTag)
, mHostPowerStateHeader(0)
#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
, mAllowPeekDelegate(nullptr)
, mAllowPokeDelegate(nullptr)
#endif
, mResponseQueueHead(0)
, mResponseQueueTail(0)
, mAllowLocalNetworkDataChange(false)
, mRequireJoinExistingNetwork(false)
, mPcapEnabled(false)
, mDisableStreamWrite(false)
, mShouldEmitChildTableUpdate(false)
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
, mAllowLocalServerDataChange(false)
#endif
#if OPENTHREAD_FTD
, mPreferredRouteId(0)
#endif
, mCurCommandIid(0)
#if OPENTHREAD_MTD || OPENTHREAD_FTD
, mInboundSecureIpFrameCounter(0)
, mInboundInsecureIpFrameCounter(0)
, mOutboundSecureIpFrameCounter(0)
, mOutboundInsecureIpFrameCounter(0)
, mDroppedOutboundIpFrameCounter(0)
, mDroppedInboundIpFrameCounter(0)
#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
, mSrpClientCallbackEnabled(false)
#endif
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
, mFramingErrorCounter(0)
, mRxSpinelFrameCounter(0)
, mRxSpinelOutOfOrderTidCounter(0)
, mTxSpinelFrameCounter(0)
, mDidInitialUpdates(false)
, mLogTimestampBase(0)
{
OT_ASSERT(mInstance != nullptr);
sNcpInstance = this;
mTxFrameBuffer.SetFrameRemovedCallback(&NcpBase::HandleFrameRemovedFromNcpBuffer, this);
memset(&mResponseQueue, 0, sizeof(mResponseQueue));
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
memset(mCurTransmitTID, 0, sizeof(mCurTransmitTID));
memset(mSrcMatchEnabled, 0, sizeof(mSrcMatchEnabled));
memset(mCurScanChannel, kInvalidScanChannel, sizeof(mCurScanChannel));
#endif
memset(mIsRawStreamEnabled, 0, sizeof(mIsRawStreamEnabled));
memset(mNextExpectedTid, 0, sizeof(mNextExpectedTid));
#if OPENTHREAD_MTD || OPENTHREAD_FTD
otMessageQueueInit(&mMessageQueue);
IgnoreError(otSetStateChangedCallback(mInstance, &NcpBase::HandleStateChanged, this));
otIp6SetReceiveCallback(mInstance, &NcpBase::HandleDatagramFromStack, this);
otIp6SetReceiveFilterEnabled(mInstance, true);
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
otNetworkTimeSyncSetCallback(mInstance, &NcpBase::HandleTimeSyncUpdate, this);
#endif // OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
#if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
otUdpForwardSetForwarder(mInstance, &NcpBase::HandleUdpForwardStream, this);
#endif
otIcmp6SetEchoMode(mInstance, OT_ICMP6_ECHO_HANDLER_DISABLED);
#if OPENTHREAD_FTD
otThreadRegisterNeighborTableCallback(mInstance, &NcpBase::HandleNeighborTableChanged);
#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
memset(&mSteeringDataAddress, 0, sizeof(mSteeringDataAddress));
#endif
#if OPENTHREAD_CONFIG_MLE_PARENT_RESPONSE_CALLBACK_API_ENABLE
otThreadRegisterParentResponseCallback(mInstance, &NcpBase::HandleParentResponseInfo, static_cast<void *>(this));
#endif
#endif // OPENTHREAD_FTD
#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
otSrpClientSetCallback(mInstance, HandleSrpClientCallback, this);
#endif
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
mChangedPropsSet.AddLastStatus(SPINEL_STATUS_RESET_UNKNOWN);
mUpdateChangedPropsTask.Post();
#if OPENTHREAD_ENABLE_VENDOR_EXTENSION
aInstance->Get<Extension::ExtensionBase>().SignalNcpInit(*this);
#endif
}
NcpBase *NcpBase::GetNcpInstance(void) { return sNcpInstance; }
spinel_iid_t NcpBase::GetCurCommandIid(void) const { return mCurCommandIid; }
void NcpBase::ResetCounters(void)
{
mFramingErrorCounter = 0;
mRxSpinelFrameCounter = 0;
mRxSpinelOutOfOrderTidCounter = 0;
mTxSpinelFrameCounter = 0;
#if OPENTHREAD_MTD || OPENTHREAD_FTD
mInboundSecureIpFrameCounter = 0;
mInboundInsecureIpFrameCounter = 0;
mOutboundSecureIpFrameCounter = 0;
mOutboundInsecureIpFrameCounter = 0;
mDroppedOutboundIpFrameCounter = 0;
mDroppedInboundIpFrameCounter = 0;
#endif
}
// ----------------------------------------------------------------------------
// MARK: Serial Traffic Glue
// ----------------------------------------------------------------------------
Spinel::Buffer::FrameTag NcpBase::GetLastOutboundFrameTag(void) { return mTxFrameBuffer.InFrameGetLastTag(); }
void NcpBase::HandleReceive(const uint8_t *aBuf, uint16_t aBufLength)
{
otError error = OT_ERROR_NONE;
uint8_t header = 0;
spinel_tid_t tid = 0;
mDisableStreamWrite = true;
// Initialize the decoder with the newly received spinel frame.
mDecoder.Init(aBuf, aBufLength);
// Receiving any message from the host has the side effect of transitioning the host power state to online.
mHostPowerState = SPINEL_HOST_POWER_STATE_ONLINE;
mHostPowerStateInProgress = false;
// Skip if there is no header byte to read or this isn't a spinel frame.
SuccessOrExit(mDecoder.ReadUint8(header));
VerifyOrExit((SPINEL_HEADER_FLAG & header) == SPINEL_HEADER_FLAG);
mRxSpinelFrameCounter++;
mCurCommandIid = SPINEL_HEADER_GET_IID(header);
#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
if (mCurCommandIid > SPINEL_HEADER_IID_MAX)
#else
if (mCurCommandIid != 0)
#endif
{
IgnoreError(WriteLastStatusFrame(header, SPINEL_STATUS_INVALID_INTERFACE));
ExitNow();
}
mInstance = IidToInstance(mCurCommandIid);
if (mInstance == nullptr)
{
IgnoreError(WriteLastStatusFrame(header, SPINEL_STATUS_INVALID_INTERFACE));
ExitNow();
}
error = HandleCommand(header);
if (error != OT_ERROR_NONE)
{
IgnoreError(PrepareLastStatusResponse(header, ThreadErrorToSpinelStatus(error)));
}
if (!IsResponseQueueEmpty())
{
// A response may have been prepared and queued for this command,
// so we attempt to send/write any queued responses. Note that
// if the response was prepared but cannot be sent now (not
// enough buffer space available), it will be attempted again
// from `HandleFrameRemovedFromNcpBuffer()` when buffer space
// becomes available.
IgnoreError(SendQueuedResponses());
}
// Check for out of sequence TIDs and update `mNextExpectedTid`,
tid = SPINEL_HEADER_GET_TID(header);
if ((mNextExpectedTid[mCurCommandIid] != 0) && (tid != mNextExpectedTid[mCurCommandIid]))
{
mRxSpinelOutOfOrderTidCounter++;
}
mNextExpectedTid[mCurCommandIid] = SPINEL_GET_NEXT_TID(tid);
exit:
mDisableStreamWrite = false;
}
void NcpBase::HandleFrameRemovedFromNcpBuffer(void *aContext,
Spinel::Buffer::FrameTag aFrameTag,
Spinel::Buffer::Priority aPriority,
Spinel::Buffer *aNcpBuffer)
{
OT_UNUSED_VARIABLE(aNcpBuffer);
OT_UNUSED_VARIABLE(aPriority);
static_cast<NcpBase *>(aContext)->HandleFrameRemovedFromNcpBuffer(aFrameTag);
}
void NcpBase::HandleFrameRemovedFromNcpBuffer(Spinel::Buffer::FrameTag aFrameTag)
{
if (mHostPowerStateInProgress)
{
if (aFrameTag == mHostPowerReplyFrameTag)
{
mHostPowerStateInProgress = false;
}
}
// A frame was removed from NCP TX buffer, so more space is now available.
// We attempt to write/send any pending frames. Order of the checks
// below is important: First any queued command responses, then
// any queued IPv6 datagram messages, then any asynchronous property updates.
// If a frame still can not fit in the available buffer, we exit immediately
// and wait for next time this callback is invoked (when another frame is
// removed and more buffer space becomes available).
SuccessOrExit(SendQueuedResponses());
#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
VendorHandleFrameRemovedFromNcpBuffer(aFrameTag);
#endif
// Check if `HOST_POWER_STATE` property update is required.
if (mHostPowerStateHeader)
{
SuccessOrExit(WritePropertyValueIsFrame(mHostPowerStateHeader, SPINEL_PROP_HOST_POWER_STATE));
mHostPowerStateHeader = 0;
if (mHostPowerState != SPINEL_HOST_POWER_STATE_ONLINE)
{
mHostPowerReplyFrameTag = GetLastOutboundFrameTag();
mHostPowerStateInProgress = true;
}
}
#if OPENTHREAD_MTD || OPENTHREAD_FTD
// Send any queued IPv6 datagram message.
SuccessOrExit(SendQueuedDatagramMessages());
#endif
// Send any unsolicited event-triggered property updates.
UpdateChangedProps();
exit:
return;
}
bool NcpBase::ShouldWakeHost(void)
{
return (mHostPowerState != SPINEL_HOST_POWER_STATE_ONLINE && !mHostPowerStateInProgress);
}
bool NcpBase::ShouldDeferHostSend(void)
{
return (mHostPowerState == SPINEL_HOST_POWER_STATE_DEEP_SLEEP && !mHostPowerStateInProgress);
}
void NcpBase::IncrementFrameErrorCounter(void) { mFramingErrorCounter++; }
otError NcpBase::StreamWrite(int aStreamId, const uint8_t *aDataPtr, int aDataLen)
{
otError error = OT_ERROR_NONE;
uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID;
spinel_prop_key_t streamPropKey;
if (aStreamId == 0)
{
streamPropKey = SPINEL_PROP_STREAM_DEBUG;
}
else
{
streamPropKey = static_cast<spinel_prop_key_t>(aStreamId);
}
VerifyOrExit(!mDisableStreamWrite, error = OT_ERROR_INVALID_STATE);
VerifyOrExit(!mChangedPropsSet.IsPropertyFiltered(streamPropKey), error = OT_ERROR_INVALID_STATE);
// If there is a pending queued response we do not allow any new log
// stream writes. This is to ensure that log messages can not continue
// to use the NCP buffer space and block other spinel frames.
VerifyOrExit(IsResponseQueueEmpty(), error = OT_ERROR_NO_BUFS);
SuccessOrExit(error = mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, streamPropKey));
SuccessOrExit(error = mEncoder.WriteData(aDataPtr, static_cast<uint16_t>(aDataLen)));
SuccessOrExit(error = mEncoder.EndFrame());
exit:
if (error == OT_ERROR_NO_BUFS)
{
mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM);
mUpdateChangedPropsTask.Post();
}
return error;
}
uint8_t NcpBase::ConvertLogLevel(otLogLevel aLogLevel)
{
uint8_t spinelLogLevel = SPINEL_NCP_LOG_LEVEL_EMERG;
switch (aLogLevel)
{
case OT_LOG_LEVEL_NONE:
spinelLogLevel = SPINEL_NCP_LOG_LEVEL_EMERG;
break;
case OT_LOG_LEVEL_CRIT:
spinelLogLevel = SPINEL_NCP_LOG_LEVEL_CRIT;
break;
case OT_LOG_LEVEL_WARN:
spinelLogLevel = SPINEL_NCP_LOG_LEVEL_WARN;
break;
case OT_LOG_LEVEL_NOTE:
spinelLogLevel = SPINEL_NCP_LOG_LEVEL_NOTICE;
break;
case OT_LOG_LEVEL_INFO:
spinelLogLevel = SPINEL_NCP_LOG_LEVEL_INFO;
break;
case OT_LOG_LEVEL_DEBG:
spinelLogLevel = SPINEL_NCP_LOG_LEVEL_DEBUG;
break;
}
return spinelLogLevel;
}
unsigned int NcpBase::ConvertLogRegion(otLogRegion aLogRegion)
{
unsigned int spinelLogRegion = SPINEL_NCP_LOG_REGION_NONE;
switch (aLogRegion)
{
case OT_LOG_REGION_API:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_API;
break;
case OT_LOG_REGION_MLE:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_MLE;
break;
case OT_LOG_REGION_ARP:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_ARP;
break;
case OT_LOG_REGION_NET_DATA:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_NET_DATA;
break;
case OT_LOG_REGION_ICMP:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_ICMP;
break;
case OT_LOG_REGION_IP6:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_IP6;
break;
case OT_LOG_REGION_TCP:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_TCP;
break;
case OT_LOG_REGION_MAC:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_MAC;
break;
case OT_LOG_REGION_MEM:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_MEM;
break;
case OT_LOG_REGION_NCP:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_NCP;
break;
case OT_LOG_REGION_MESH_COP:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_MESH_COP;
break;
case OT_LOG_REGION_NET_DIAG:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_NET_DIAG;
break;
case OT_LOG_REGION_PLATFORM:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_PLATFORM;
break;
case OT_LOG_REGION_COAP:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_COAP;
break;
case OT_LOG_REGION_CLI:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_CLI;
break;
case OT_LOG_REGION_CORE:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_CORE;
break;
case OT_LOG_REGION_UTIL:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_UTIL;
break;
case OT_LOG_REGION_BBR:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_BBR;
break;
case OT_LOG_REGION_MLR:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_MLR;
break;
case OT_LOG_REGION_DUA:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_DUA;
break;
case OT_LOG_REGION_BR:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_BR;
break;
case OT_LOG_REGION_SRP:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_SRP;
break;
case OT_LOG_REGION_DNS:
spinelLogRegion = SPINEL_NCP_LOG_REGION_OT_DNS;
break;
}
return spinelLogRegion;
}
void NcpBase::Log(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aLogString)
{
otError error = OT_ERROR_NONE;
uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID;
VerifyOrExit(!mDisableStreamWrite, error = OT_ERROR_INVALID_STATE);
VerifyOrExit(!mChangedPropsSet.IsPropertyFiltered(SPINEL_PROP_STREAM_LOG));
// If there is a pending queued response we do not allow any new log
// stream writes. This is to ensure that log messages can not continue
// to use the NCP buffer space and block other spinel frames.
VerifyOrExit(IsResponseQueueEmpty(), error = OT_ERROR_NO_BUFS);
SuccessOrExit(error = mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_STREAM_LOG));
SuccessOrExit(error = mEncoder.WriteUtf8(aLogString));
SuccessOrExit(error = mEncoder.WriteUint8(ConvertLogLevel(aLogLevel)));
SuccessOrExit(error = mEncoder.WriteUintPacked(ConvertLogRegion(aLogRegion)));
SuccessOrExit(error = mEncoder.WriteUint64(mLogTimestampBase + otPlatAlarmMilliGetNow()));
SuccessOrExit(error = mEncoder.EndFrame());
exit:
if (error == OT_ERROR_NO_BUFS)
{
mChangedPropsSet.AddLastStatus(SPINEL_STATUS_NOMEM);
mUpdateChangedPropsTask.Post();
}
}
#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
void NcpBase::RegisterPeekPokeDelegates(otNcpDelegateAllowPeekPoke aAllowPeekDelegate,
otNcpDelegateAllowPeekPoke aAllowPokeDelegate)
{
mAllowPeekDelegate = aAllowPeekDelegate;
mAllowPokeDelegate = aAllowPokeDelegate;
}
#endif // OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
// ----------------------------------------------------------------------------
// MARK: Spinel Response Handling
// ----------------------------------------------------------------------------
uint8_t NcpBase::GetWrappedResponseQueueIndex(uint8_t aPosition)
{
while (aPosition >= kResponseQueueSize)
{
aPosition -= kResponseQueueSize;
}
return aPosition;
}
otError NcpBase::EnqueueResponse(uint8_t aHeader, ResponseType aType, unsigned int aPropKeyOrStatus)
{
otError error = OT_ERROR_NONE;
spinel_iid_t iid = SPINEL_HEADER_GET_IID(aHeader);
spinel_tid_t tid = SPINEL_HEADER_GET_TID(aHeader);
ResponseEntry *entry;
if (tid == 0)
{
// No response is required for TID zero. But we may emit a
// `LAST_STATUS` error status (if not filtered) for TID
// zero (e.g., for a dropped `STREAM_NET` set command).
if (aType == kResponseTypeLastStatus)
{
mChangedPropsSet.AddLastStatus(static_cast<spinel_status_t>(aPropKeyOrStatus));
}
ExitNow();
}
if ((mResponseQueueTail - mResponseQueueHead) >= kResponseQueueSize)
{
// If there is no room a for a response, emit an unsolicited
// `DROPPED` error status to indicate a spinel response was
// dropped.
mChangedPropsSet.AddLastStatus(SPINEL_STATUS_DROPPED);
ExitNow(error = OT_ERROR_NO_BUFS);
}
// Transaction IDs are expected to come in sequence, if however, we
// get an out of sequence TID, check if we already have a response
// queued for this TID and if so mark the old entry as deleted.
if (tid != mNextExpectedTid[iid])
{
for (uint8_t cur = mResponseQueueHead; cur < mResponseQueueTail; cur++)
{
entry = &mResponseQueue[GetWrappedResponseQueueIndex(cur)];
if (entry->mIsInUse && (entry->mIid == iid) && (entry->mTid == tid))
{
// Entry is just marked here and will be removed
// from `SendQueuedResponses()`.
entry->mIsInUse = false;
break;
}
}
}
// Add the new entry in the queue at tail.
entry = &mResponseQueue[GetWrappedResponseQueueIndex(mResponseQueueTail)];
entry->mIid = iid;
entry->mTid = tid;
entry->mIsInUse = true;
entry->mType = aType;
entry->mPropKeyOrStatus = aPropKeyOrStatus;
mResponseQueueTail++;
exit:
return error;
}
otError NcpBase::SendQueuedResponses(void)
{
otError error = OT_ERROR_NONE;
while (mResponseQueueHead != mResponseQueueTail)
{
ResponseEntry &entry = mResponseQueue[mResponseQueueHead];
if (entry.mIsInUse)
{
uint8_t header = SPINEL_HEADER_FLAG;
header |= SPINEL_HEADER_IID(entry.mIid);
header |= static_cast<uint8_t>(entry.mTid << SPINEL_HEADER_TID_SHIFT);
if (entry.mType == kResponseTypeLastStatus)
{
spinel_status_t status = static_cast<spinel_status_t>(entry.mPropKeyOrStatus);
SuccessOrExit(error = WriteLastStatusFrame(header, status));
}
else
{
spinel_prop_key_t propKey = static_cast<spinel_prop_key_t>(entry.mPropKeyOrStatus);
bool isGetResponse = (entry.mType == kResponseTypeGet);
SuccessOrExit(error = WritePropertyValueIsFrame(header, propKey, isGetResponse));
}
}
// Remove the response entry.
entry.mIsInUse = false;
mResponseQueueHead++;
if (mResponseQueueHead == kResponseQueueSize)
{
// Only when `head` wraps, the `tail` will be wrapped as well.
//
// This ensures that `tail` is always bigger than `head` and
// `(tail - head)` to correctly give the number of items in
// the queue.
mResponseQueueHead = 0;
mResponseQueueTail = GetWrappedResponseQueueIndex(mResponseQueueTail);
}
}
exit:
return error;
}
// ----------------------------------------------------------------------------
// MARK: Property/Status Changed
// ----------------------------------------------------------------------------
void NcpBase::UpdateChangedProps(Tasklet &aTasklet)
{
OT_UNUSED_VARIABLE(aTasklet);
GetNcpInstance()->UpdateChangedProps();
}
void NcpBase::UpdateChangedProps(void)
{
uint8_t numEntries;
spinel_prop_key_t propKey;
const ChangedPropsSet::Entry *entry;
#if OPENTHREAD_MTD || OPENTHREAD_FTD
ProcessThreadChangedFlags();
#endif
VerifyOrExit(!mChangedPropsSet.IsEmpty());
entry = mChangedPropsSet.GetSupportedEntries(numEntries);
for (uint8_t index = 0; index < numEntries; index++, entry++)
{
if (!mChangedPropsSet.IsEntryChanged(index))
{
continue;
}
propKey = entry->mPropKey;
if (propKey == SPINEL_PROP_LAST_STATUS)
{
spinel_status_t status = entry->mStatus;
if (status == SPINEL_STATUS_RESET_UNKNOWN)
{
status = ResetReasonToSpinelStatus(otPlatGetResetReason(mInstance));
}
SuccessOrExit(WriteLastStatusFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID, status));
}
else if (mDidInitialUpdates)
{
SuccessOrExit(WritePropertyValueIsFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID, propKey));
}
mChangedPropsSet.RemoveEntry(index);
VerifyOrExit(!mChangedPropsSet.IsEmpty());
}
exit:
mDidInitialUpdates = true;
}
// ----------------------------------------------------------------------------
// MARK: Inbound Command Handler
// ----------------------------------------------------------------------------
otError NcpBase::HandleCommand(uint8_t aHeader)
{
otError error = OT_ERROR_NONE;
unsigned int command;
SuccessOrExit(error = mDecoder.ReadUintPacked(command));
switch (command)
{
case SPINEL_CMD_NOOP:
error = CommandHandler_NOOP(aHeader);
break;
case SPINEL_CMD_RESET:
error = CommandHandler_RESET(aHeader);
break;
case SPINEL_CMD_PROP_VALUE_GET:
case SPINEL_CMD_PROP_VALUE_SET:
case SPINEL_CMD_PROP_VALUE_INSERT:
case SPINEL_CMD_PROP_VALUE_REMOVE:
error = CommandHandler_PROP_VALUE_update(aHeader, command);
break;
#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
case SPINEL_CMD_PEEK:
error = CommandHandler_PEEK(aHeader);
break;
case SPINEL_CMD_POKE:
error = CommandHandler_POKE(aHeader);
break;
#endif
#if OPENTHREAD_MTD || OPENTHREAD_FTD
case SPINEL_CMD_NET_SAVE:
case SPINEL_CMD_NET_RECALL:
error = OT_ERROR_NOT_IMPLEMENTED;
break;
case SPINEL_CMD_NET_CLEAR:
error = CommandHandler_NET_CLEAR(aHeader);
break;
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
default:
#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
if (command >= SPINEL_CMD_VENDOR__BEGIN && command < SPINEL_CMD_VENDOR__END)
{
error = VendorCommandHandler(aHeader, command);
break;
}
#endif
error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_INVALID_COMMAND);
break;
}
exit:
return error;
}
// ----------------------------------------------------------------------------
// MARK: Property Get/Set/Insert/Remove Commands
// ----------------------------------------------------------------------------
// Returns `true` and updates the `aError` on success.
bool NcpBase::HandlePropertySetForSpecialProperties(uint8_t aHeader, spinel_prop_key_t aKey, otError &aError)
{
bool didHandle = true;
// Here the properties that require special treatment are handled.
// These properties are expected to form/write the response from
// their set handler directly.
switch (aKey)
{
case SPINEL_PROP_HOST_POWER_STATE:
ExitNow(aError = HandlePropertySet_SPINEL_PROP_HOST_POWER_STATE(aHeader));
#if OPENTHREAD_CONFIG_DIAG_ENABLE
case SPINEL_PROP_NEST_STREAM_MFG:
ExitNow(aError = HandlePropertySet_SPINEL_PROP_NEST_STREAM_MFG(aHeader));
#endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
case SPINEL_PROP_MESHCOP_COMMISSIONER_GENERATE_PSKC:
ExitNow(aError = HandlePropertySet_SPINEL_PROP_MESHCOP_COMMISSIONER_GENERATE_PSKC(aHeader));
case SPINEL_PROP_THREAD_COMMISSIONER_ENABLED:
ExitNow(aError = HandlePropertySet_SPINEL_PROP_THREAD_COMMISSIONER_ENABLED(aHeader));
#endif
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
case SPINEL_PROP_STREAM_RAW:
ExitNow(aError = HandlePropertySet_SPINEL_PROP_STREAM_RAW(aHeader));
#endif
default:
didHandle = false;
break;
}
exit:
return didHandle;
}
otError NcpBase::HandleCommandPropertySet(uint8_t aHeader, spinel_prop_key_t aKey)
{
otError error = OT_ERROR_NONE;
PropertyHandler handler = FindSetPropertyHandler(aKey);
if (handler != nullptr)
{
mDisableStreamWrite = false;
error = (this->*handler)();
mDisableStreamWrite = true;
}
else
{
// If there is no "set" handler, check if this property is one of the
// ones that require different treatment.
bool didHandle = HandlePropertySetForSpecialProperties(aHeader, aKey, error);
VerifyOrExit(!didHandle);
#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
if (aKey >= SPINEL_PROP_VENDOR__BEGIN && aKey < SPINEL_PROP_VENDOR__END)
{
mDisableStreamWrite = false;
error = VendorSetPropertyHandler(aKey);
mDisableStreamWrite = true;
// An `OT_ERROR_NOT_FOUND` status from vendor handler indicates
// that it does not support the given property key. In that
// case, `didHandle` is set to `false` so a `LAST_STATUS` with
// `PROP_NOT_FOUND` is emitted. Otherwise, we fall through to
// prepare the response.
didHandle = (error != OT_ERROR_NOT_FOUND);
}
#endif
VerifyOrExit(didHandle, error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_PROP_NOT_FOUND));
}
if (error == OT_ERROR_NONE)
{
error = PrepareSetResponse(aHeader, aKey);
}
else
{
error = PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(error));
}
exit:
return error;
}
otError NcpBase::HandleCommandPropertyInsertRemove(uint8_t aHeader, spinel_prop_key_t aKey, unsigned int aCommand)
{
otError error = OT_ERROR_NONE;
PropertyHandler handler = nullptr;
unsigned int responseCommand = 0;
const uint8_t *valuePtr;
uint16_t valueLen;
switch (aCommand)
{
case SPINEL_CMD_PROP_VALUE_INSERT:
handler = FindInsertPropertyHandler(aKey);
responseCommand = SPINEL_CMD_PROP_VALUE_INSERTED;
break;
case SPINEL_CMD_PROP_VALUE_REMOVE:
handler = FindRemovePropertyHandler(aKey);
responseCommand = SPINEL_CMD_PROP_VALUE_REMOVED;
break;
default:
OT_ASSERT(false);
}
VerifyOrExit(handler != nullptr, error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_PROP_NOT_FOUND));
// Save current read position in the decoder. Read the entire
// content as a data blob (which is used in forming the response
// in case of success), then reset the read position back so
// that the `PropertyHandler` method can parse the content.
mDecoder.SavePosition();
IgnoreError(mDecoder.ReadData(valuePtr, valueLen));
IgnoreError(mDecoder.ResetToSaved());
mDisableStreamWrite = false;
error = (this->*handler)();
mDisableStreamWrite = true;
VerifyOrExit(error == OT_ERROR_NONE, error = PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(error)));
error = WritePropertyValueInsertedRemovedFrame(aHeader, responseCommand, aKey, valuePtr, valueLen);
// If the full response cannot be written now, instead prepare
// a `LAST_STATUS(STATUS_OK)` update as response.
if (error != OT_ERROR_NONE)
{
error = PrepareLastStatusResponse(aHeader, SPINEL_STATUS_OK);
}
exit:
return error;
}
// ----------------------------------------------------------------------------
// MARK: Outbound Frame Methods
// ----------------------------------------------------------------------------
otError NcpBase::WriteLastStatusFrame(uint8_t aHeader, spinel_status_t aLastStatus)
{
otError error = OT_ERROR_NONE;
if (SPINEL_HEADER_GET_IID(aHeader) == SPINEL_HEADER_GET_IID(SPINEL_HEADER_TX_NOTIFICATION_IID))
{
mLastStatus = aLastStatus;
}
SuccessOrExit(error = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_LAST_STATUS));
SuccessOrExit(error = mEncoder.WriteUintPacked(aLastStatus));
SuccessOrExit(error = mEncoder.EndFrame());
exit:
return error;
}
otError NcpBase::WritePropertyValueIsFrame(uint8_t aHeader, spinel_prop_key_t aPropKey, bool aIsGetResponse)
{
otError error = OT_ERROR_NONE;
PropertyHandler handler = FindGetPropertyHandler(aPropKey);
if (handler != nullptr)
{
SuccessOrExit(error = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PROP_VALUE_IS, aPropKey));
SuccessOrExit(error = (this->*handler)());
ExitNow(error = mEncoder.EndFrame());
}
#if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK
if (aPropKey >= SPINEL_PROP_VENDOR__BEGIN && aPropKey < SPINEL_PROP_VENDOR__END)
{
SuccessOrExit(error = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PROP_VALUE_IS, aPropKey));
error = VendorGetPropertyHandler(aPropKey);
// An `OT_ERROR_NOT_FOUND` status from vendor handler indicates that
// it did not support the given property key. In that case, we fall
// through to prepare a `LAST_STATUS` response.
if (error != OT_ERROR_NOT_FOUND)
{
SuccessOrExit(error);
ExitNow(error = mEncoder.EndFrame());
}
}
#endif
if (aIsGetResponse)
{
SuccessOrExit(error = WriteLastStatusFrame(aHeader, SPINEL_STATUS_PROP_NOT_FOUND));
}
else
{
// Send a STATUS_OK for "set" response to a property that
// has no corresponding get handler.
SuccessOrExit(error = WriteLastStatusFrame(aHeader, SPINEL_STATUS_OK));
}
exit:
return error;
}
otError NcpBase::WritePropertyValueInsertedRemovedFrame(uint8_t aHeader,
unsigned int aResponseCommand,
spinel_prop_key_t aPropKey,
const uint8_t *aValuePtr,
uint16_t aValueLen)
{
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mEncoder.BeginFrame(aHeader, aResponseCommand, aPropKey));
SuccessOrExit(error = mEncoder.WriteData(aValuePtr, aValueLen));
SuccessOrExit(error = mEncoder.EndFrame());
exit:
return error;
}
// ----------------------------------------------------------------------------
// MARK: Individual Command Handlers
// ----------------------------------------------------------------------------
otError NcpBase::CommandHandler_NOOP(uint8_t aHeader) { return PrepareLastStatusResponse(aHeader, SPINEL_STATUS_OK); }
otError NcpBase::CommandHandler_RESET(uint8_t aHeader)
{
OT_UNUSED_VARIABLE(aHeader);
otError error = OT_ERROR_NONE;
uint8_t reset_type = SPINEL_RESET_STACK;
if (mDecoder.GetRemainingLengthInStruct() > 0)
{
SuccessOrAssert(error = mDecoder.ReadUint8(reset_type));
}
#if OPENTHREAD_RADIO
if (reset_type == SPINEL_RESET_STACK)
{
otInstanceResetRadioStack(mInstance);
mIsRawStreamEnabled[mCurCommandIid] = false;
mCurTransmitTID[mCurCommandIid] = 0;
mCurScanChannel[mCurCommandIid] = kInvalidScanChannel;
mSrcMatchEnabled[mCurCommandIid] = false;
ResetCounters();
SuccessOrAssert(error = WriteLastStatusFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID,
SPINEL_STATUS_RESET_POWER_ON));
}
#if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE
else if (reset_type == SPINEL_RESET_BOOTLOADER)
{
// Signal a platform reset to bootloader mode.
// If implemented, this function shouldn't return.
error = otInstanceResetToBootloader(mInstance);
}
#endif
else
#endif
{
// Signal a platform reset. If implemented, this function
// shouldn't return.
otInstanceReset(mInstance);
#if OPENTHREAD_MTD || OPENTHREAD_FTD
// We only get to this point if the
// platform doesn't support resetting.
// In such a case we fake it.
IgnoreError(otThreadSetEnabled(mInstance, false));
IgnoreError(otIp6SetEnabled(mInstance, false));
#endif
sNcpInstance = nullptr;
}
return error;
}
otError NcpBase::CommandHandler_PROP_VALUE_update(uint8_t aHeader, unsigned int aCommand)
{
otError error = OT_ERROR_NONE;
unsigned int propKey = 0;
error = mDecoder.ReadUintPacked(propKey);
VerifyOrExit(error == OT_ERROR_NONE, error = PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(error)));
switch (aCommand)
{
case SPINEL_CMD_PROP_VALUE_GET:
error = PrepareGetResponse(aHeader, static_cast<spinel_prop_key_t>(propKey));
break;
case SPINEL_CMD_PROP_VALUE_SET:
error = HandleCommandPropertySet(aHeader, static_cast<spinel_prop_key_t>(propKey));
break;
case SPINEL_CMD_PROP_VALUE_INSERT:
case SPINEL_CMD_PROP_VALUE_REMOVE:
error = HandleCommandPropertyInsertRemove(aHeader, static_cast<spinel_prop_key_t>(propKey), aCommand);
break;
default:
break;
}
exit:
return error;
}
#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
otError NcpBase::CommandHandler_PEEK(uint8_t aHeader)
{
otError parseError = OT_ERROR_NONE;
otError responseError = OT_ERROR_NONE;
uint32_t address;
uint16_t count;
SuccessOrExit(parseError = mDecoder.ReadUint32(address));
SuccessOrExit(parseError = mDecoder.ReadUint16(count));
VerifyOrExit(count != 0, parseError = OT_ERROR_INVALID_ARGS);
if (mAllowPeekDelegate != nullptr)
{
VerifyOrExit(mAllowPeekDelegate(address, count), parseError = OT_ERROR_INVALID_ARGS);
}
SuccessOrExit(responseError = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PEEK_RET));
SuccessOrExit(responseError = mEncoder.WriteUint32(address));
SuccessOrExit(responseError = mEncoder.WriteUint16(count));
SuccessOrExit(responseError = mEncoder.WriteData(reinterpret_cast<const uint8_t *>(address), count));
SuccessOrExit(responseError = mEncoder.EndFrame());
exit:
if (parseError != OT_ERROR_NONE)
{
responseError = PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(parseError));
}
return responseError;
}
otError NcpBase::CommandHandler_POKE(uint8_t aHeader)
{
otError parseError = OT_ERROR_NONE;
uint32_t address;
uint16_t count;
const uint8_t *dataPtr = nullptr;
uint16_t dataLen;
SuccessOrExit(parseError = mDecoder.ReadUint32(address));
SuccessOrExit(parseError = mDecoder.ReadUint16(count));
SuccessOrExit(parseError = mDecoder.ReadData(dataPtr, dataLen));
VerifyOrExit(count != 0, parseError = OT_ERROR_INVALID_ARGS);
VerifyOrExit(count <= dataLen, parseError = OT_ERROR_INVALID_ARGS);
if (mAllowPokeDelegate != nullptr)
{
VerifyOrExit(mAllowPokeDelegate(address, count), parseError = OT_ERROR_INVALID_ARGS);
}
memcpy(reinterpret_cast<uint8_t *>(address), dataPtr, count);
exit:
return PrepareLastStatusResponse(aHeader, ThreadErrorToSpinelStatus(parseError));
}
#endif // OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
// ----------------------------------------------------------------------------
// MARK: Individual Property Getters and Setters
// ----------------------------------------------------------------------------
#if OPENTHREAD_CONFIG_DIAG_ENABLE
otError NcpBase::HandlePropertySet_SPINEL_PROP_NEST_STREAM_MFG(uint8_t aHeader)
{
const char *string = nullptr;
char output[OPENTHREAD_CONFIG_DIAG_OUTPUT_BUFFER_SIZE];
otError error = OT_ERROR_NONE;
error = mDecoder.ReadUtf8(string);
VerifyOrExit(error == OT_ERROR_NONE, error = WriteLastStatusFrame(aHeader, ThreadErrorToSpinelStatus(error)));
#if OPENTHREAD_MTD || OPENTHREAD_FTD
// TODO do not pass mfg prefix
// skip mfg prefix from wpantund
if (memcmp(string, "mfg ", 4) == 0)
{
string += 4;
}
#endif
SuccessOrExit(error = otDiagProcessCmdLine(mInstance, string, output, sizeof(output)));
// Prepare the response
SuccessOrExit(error = mEncoder.BeginFrame(aHeader, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_NEST_STREAM_MFG));
SuccessOrExit(error = mEncoder.WriteUtf8(output));
SuccessOrExit(error = mEncoder.EndFrame());
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_DIAG_ENABLE
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_ENABLED>(void)
{
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
return mEncoder.WriteBool(otLinkRawIsEnabled(mInstance));
#else
return mEncoder.WriteBool(false);
#endif
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_CHAN>(void)
{
return mEncoder.WriteUint8(otLinkGetChannel(mInstance));
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_CHAN>(void)
{
unsigned int channel = 0;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadUintPacked(channel));
error = otLinkSetChannel(mInstance, static_cast<uint8_t>(channel));
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
SuccessOrExit(error);
// Make sure we are update the receiving channel if raw link is enabled and we have raw
// stream enabled already
if (otLinkRawIsEnabled(mInstance) && mIsRawStreamEnabled[mCurCommandIid])
{
error = otLinkRawReceive(mInstance);
}
#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_PROMISCUOUS_MODE>(void)
{
bool isPromiscuous;
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
isPromiscuous = otLinkRawGetPromiscuous(mInstance);
#else
isPromiscuous = otLinkIsPromiscuous(mInstance);
#endif
return mEncoder.WriteUint8(isPromiscuous ? SPINEL_MAC_PROMISCUOUS_MODE_FULL : SPINEL_MAC_PROMISCUOUS_MODE_OFF);
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_PROMISCUOUS_MODE>(void)
{
uint8_t mode = 0;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadUint8(mode));
switch (mode)
{
case SPINEL_MAC_PROMISCUOUS_MODE_OFF:
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
error = otLinkRawSetPromiscuous(mInstance, false);
#else
error = otLinkSetPromiscuous(mInstance, false);
#endif
break;
case SPINEL_MAC_PROMISCUOUS_MODE_NETWORK:
case SPINEL_MAC_PROMISCUOUS_MODE_FULL:
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
error = otLinkRawSetPromiscuous(mInstance, true);
#else
error = otLinkSetPromiscuous(mInstance, true);
#endif
break;
default:
error = OT_ERROR_INVALID_ARGS;
break;
}
exit:
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_RX_ON_WHEN_IDLE_MODE>(void)
{
bool enabled;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadBool(enabled));
otPlatRadioSetRxOnWhenIdle(mInstance, enabled);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_15_4_PANID>(void)
{
return mEncoder.WriteUint16(otLinkGetPanId(mInstance));
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_15_4_PANID>(void)
{
uint16_t panid;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadUint16(panid));
error = otLinkSetPanId(mInstance, panid);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_15_4_LADDR>(void)
{
return mEncoder.WriteEui64(*otLinkGetExtendedAddress(mInstance));
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_15_4_LADDR>(void)
{
const otExtAddress *extAddress;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadEui64(extAddress));
error = otLinkSetExtendedAddress(mInstance, extAddress);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_15_4_SADDR>(void)
{
return mEncoder.WriteUint16(otLinkGetShortAddress(mInstance));
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_RAW_STREAM_ENABLED>(void)
{
return mEncoder.WriteBool(mIsRawStreamEnabled[mCurCommandIid]);
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_RAW_STREAM_ENABLED>(void)
{
bool enabled = false;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadBool(enabled));
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
if (otLinkRawIsEnabled(mInstance))
{
if (enabled)
{
error = otLinkRawReceive(mInstance);
}
else
{
error = otLinkRawSleep(mInstance);
}
}
#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
mIsRawStreamEnabled[mCurCommandIid] = enabled;
exit:
return error;
}
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE || OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RCP_CSL_ACCURACY>(void)
{
return mEncoder.WriteUint8(otPlatRadioGetCslAccuracy(mInstance));
}
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RCP_CSL_UNCERTAINTY>(void)
{
return mEncoder.WriteUint8(otPlatRadioGetCslUncertainty(mInstance));
}
#endif
otError NcpBase::EncodeChannelMask(uint32_t aChannelMask)
{
otError error = OT_ERROR_NONE;
for (uint8_t i = 0; i < 32; i++)
{
if (0 != (aChannelMask & (1 << i)))
{
SuccessOrExit(error = mEncoder.WriteUint8(i));
}
}
exit:
return error;
}
otError NcpBase::DecodeChannelMask(uint32_t &aChannelMask)
{
otError error = OT_ERROR_NONE;
uint8_t channel;
aChannelMask = 0;
while (!mDecoder.IsAllReadInStruct())
{
SuccessOrExit(error = mDecoder.ReadUint8(channel));
VerifyOrExit(channel <= 31, error = OT_ERROR_INVALID_ARGS);
aChannelMask |= (1UL << channel);
}
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SCAN_MASK>(void)
{
return EncodeChannelMask(mScanChannelMask);
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SCAN_MASK>(void)
{
uint32_t newMask = 0;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = DecodeChannelMask(newMask));
mScanChannelMask = newMask;
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SCAN_PERIOD>(void)
{
return mEncoder.WriteUint16(mScanPeriod);
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SCAN_PERIOD>(void)
{
return mDecoder.ReadUint16(mScanPeriod);
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SCAN_STATE>(void)
{
uint8_t scanState = SPINEL_SCAN_STATE_IDLE;
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
if (otLinkRawIsEnabled(mInstance))
{
scanState = (mCurScanChannel[mCurCommandIid] == kInvalidScanChannel) ? SPINEL_SCAN_STATE_IDLE
: SPINEL_SCAN_STATE_ENERGY;
}
else
#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
{
#if OPENTHREAD_MTD || OPENTHREAD_FTD
if (otLinkIsActiveScanInProgress(mInstance))
{
scanState = SPINEL_SCAN_STATE_BEACON;
}
else if (otLinkIsEnergyScanInProgress(mInstance))
{
scanState = SPINEL_SCAN_STATE_ENERGY;
}
else if (otThreadIsDiscoverInProgress(mInstance))
{
scanState = SPINEL_SCAN_STATE_DISCOVER;
}
else
{
scanState = SPINEL_SCAN_STATE_IDLE;
}
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
}
return mEncoder.WriteUint8(scanState);
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SCAN_STATE>(void)
{
uint8_t state = 0;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadUint8(state));
switch (state)
{
case SPINEL_SCAN_STATE_IDLE:
error = OT_ERROR_NONE;
break;
#if OPENTHREAD_MTD || OPENTHREAD_FTD
case SPINEL_SCAN_STATE_BEACON:
error = otLinkActiveScan(mInstance, mScanChannelMask, mScanPeriod, &HandleActiveScanResult_Jump, this);
SuccessOrExit(error);
break;
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
case SPINEL_SCAN_STATE_ENERGY:
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
if (otLinkRawIsEnabled(mInstance))
{
uint8_t scanChannel;
// Make sure we aren't already scanning and that we have
// only 1 bit set for the channel mask.
VerifyOrExit(mCurScanChannel[mCurCommandIid] == kInvalidScanChannel, error = OT_ERROR_INVALID_STATE);
VerifyOrExit(HasOnly1BitSet(mScanChannelMask), error = OT_ERROR_INVALID_ARGS);
scanChannel = IndexOfMSB(mScanChannelMask);
mCurScanChannel[mCurCommandIid] = static_cast<int8_t>(scanChannel);
error = otLinkRawEnergyScan(mInstance, scanChannel, mScanPeriod, LinkRawEnergyScanDone);
}
else
#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
{
#if OPENTHREAD_MTD || OPENTHREAD_FTD
error = otLinkEnergyScan(mInstance, mScanChannelMask, mScanPeriod, &HandleEnergyScanResult_Jump, this);
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
}
SuccessOrExit(error);
break;
#if OPENTHREAD_MTD || OPENTHREAD_FTD
case SPINEL_SCAN_STATE_DISCOVER:
error = otThreadDiscover(mInstance, mScanChannelMask, mDiscoveryScanPanId, mDiscoveryScanJoinerFlag,
mDiscoveryScanEnableFiltering, &HandleActiveScanResult_Jump, this);
SuccessOrExit(error);
break;
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
default:
error = OT_ERROR_NOT_IMPLEMENTED;
break;
}
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_UNSOL_UPDATE_FILTER>(void)
{
otError error = OT_ERROR_NONE;
uint8_t numEntries;
const ChangedPropsSet::Entry *entry;
entry = mChangedPropsSet.GetSupportedEntries(numEntries);
for (uint8_t index = 0; index < numEntries; index++, entry++)
{
if (mChangedPropsSet.IsEntryFiltered(index))
{
SuccessOrExit(error = mEncoder.WriteUintPacked(entry->mPropKey));
}
}
exit:
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_UNSOL_UPDATE_FILTER>(void)
{
unsigned int propKey;
otError error = OT_ERROR_NONE;
// First clear the current filter.
mChangedPropsSet.ClearFilter();
while (mDecoder.GetRemainingLengthInStruct() > 0)
{
SuccessOrExit(error = mDecoder.ReadUintPacked(propKey));
IgnoreError(mChangedPropsSet.EnablePropertyFilter(static_cast<spinel_prop_key_t>(propKey), true));
}
exit:
// If we had an error, we may have actually changed
// the state of the filter, So we need to report
// those incomplete changes via an asynchronous
// change event.
if (error != OT_ERROR_NONE)
{
IgnoreError(WritePropertyValueIsFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID,
SPINEL_PROP_UNSOL_UPDATE_FILTER));
}
return error;
}
template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_UNSOL_UPDATE_FILTER>(void)
{
otError error = OT_ERROR_NONE;
unsigned int propKey;
SuccessOrExit(error = mDecoder.ReadUintPacked(propKey));
error = mChangedPropsSet.EnablePropertyFilter(static_cast<spinel_prop_key_t>(propKey), true);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyRemove<SPINEL_PROP_UNSOL_UPDATE_FILTER>(void)
{
otError error = OT_ERROR_NONE;
unsigned int propKey;
SuccessOrExit(error = mDecoder.ReadUintPacked(propKey));
error = mChangedPropsSet.EnablePropertyFilter(static_cast<spinel_prop_key_t>(propKey), false);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_LAST_STATUS>(void)
{
return mEncoder.WriteUintPacked(mLastStatus);
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PROTOCOL_VERSION>(void)
{
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_PROTOCOL_VERSION_THREAD_MAJOR));
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_PROTOCOL_VERSION_THREAD_MINOR));
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_INTERFACE_TYPE>(void)
{
return mEncoder.WriteUintPacked(SPINEL_PROTOCOL_TYPE_THREAD);
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_VENDOR_ID>(void)
{
return mEncoder.WriteUintPacked(0); // Vendor ID. Zero for unknown.
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_CAPS>(void)
{
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_COUNTERS));
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_UNSOL_UPDATE_FILTER));
#if OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MCU_POWER_STATE));
#endif
#if OPENTHREAD_CONFIG_RADIO_2P4GHZ_OQPSK_SUPPORT
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_802_15_4_2450MHZ_OQPSK));
#endif
#if OPENTHREAD_CONFIG_RADIO_915MHZ_OQPSK_SUPPORT
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_802_15_4_915MHZ_OQPSK));
#endif
#if OPENTHREAD_FTD
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CONFIG_FTD));
#elif OPENTHREAD_MTD
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CONFIG_MTD));
#elif OPENTHREAD_RADIO
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CONFIG_RADIO));
#endif
#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MAC_RAW));
#endif
#if OPENTHREAD_RADIO
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_RCP_API_VERSION));
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_RCP_MIN_HOST_API_VERSION));
#endif
#if OPENTHREAD_CONFIG_PLATFORM_BOOTLOADER_MODE_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_RCP_RESET_TO_BOOTLOADER));
#endif
#if OPENTHREAD_PLATFORM_POSIX
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_POSIX));
#endif
#if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_APP)
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_OPENTHREAD_LOG_METADATA));
#endif
#if OPENTHREAD_MTD || OPENTHREAD_FTD
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_NET_THREAD_1_1));
#if (OPENTHREAD_CONFIG_THREAD_VERSION >= OT_THREAD_VERSION_1_2)
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_NET_THREAD_1_2));
#endif
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_PCAP));
#if OPENTHREAD_CONFIG_MAC_FILTER_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MAC_ALLOWLIST));
#endif
#if OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_JAM_DETECT));
#endif
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CHILD_SUPERVISION));
#if OPENTHREAD_CONFIG_CHANNEL_MONITOR_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CHANNEL_MONITOR));
#endif
#if OPENTHREAD_CONFIG_CHANNEL_MANAGER_ENABLE && OPENTHREAD_FTD
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_CHANNEL_MANAGER));
#endif
#if OPENTHREAD_CONFIG_TIME_SYNC_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_TIME_SYNC));
#endif
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_ERROR_RATE_TRACKING));
#if OPENTHREAD_CONFIG_MLE_STEERING_DATA_SET_OOB_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_OOB_STEERING_DATA));
#endif
#if OPENTHREAD_CONFIG_IP6_SLAAC_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_SLAAC));
#endif
#if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_RADIO_COEX));
#endif
#if OPENTHREAD_CONFIG_MAC_RETRY_SUCCESS_HISTOGRAM_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MAC_RETRY_HISTOGRAM));
#endif
#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_PEEK_POKE));
#endif
#if OPENTHREAD_CONFIG_MLE_MAX_CHILDREN > 0
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_ROLE_ROUTER));
#endif
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_ROLE_SLEEPY));
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_COMMISSIONER_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_COMMISSIONER));
#endif
#if OPENTHREAD_CONFIG_JOINER_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_JOINER));
#endif
#if OPENTHREAD_CONFIG_BORDER_ROUTER_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_BORDER_ROUTER));
#endif
#if OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_UDP_FORWARD));
#endif
#if OPENTHREAD_CONFIG_TMF_NETDATA_SERVICE_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_SERVICE));
#endif
#if OPENTHREAD_CONFIG_MAC_CSL_RECEIVER_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_CSL_RECEIVER));
#endif
#if OPENTHREAD_CONFIG_MULTI_RADIO
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_MULTI_RADIO));
#endif
#if OPENTHREAD_CONFIG_SRP_CLIENT_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_SRP_CLIENT));
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_LINK_METRICS));
#endif
#if OPENTHREAD_CONFIG_DUA_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_DUA));
#endif
#if OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_REFERENCE_DEVICE));
#endif
#if OPENTHREAD_FTD && OPENTHREAD_CONFIG_BACKBONE_ROUTER_ENABLE
SuccessOrExit(error = mEncoder.WriteUintPacked(SPINEL_CAP_THREAD_BACKBONE_ROUTER));
#endif
#endif // OPENTHREAD_MTD || OPENTHREAD_FTD
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_NCP_VERSION>(void)
{
return mEncoder.WriteUtf8(otGetVersionString());
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_INTERFACE_COUNT>(void)
{
uint8_t instances = 1;
#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO
instances = 0;
for (uint8_t i = 0; i <= SPINEL_HEADER_IID_MAX; i++)
{
if (mInstances[i] != nullptr)
{
instances++;
}
}
#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO
return mEncoder.WriteUint8(instances);
}
#if OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MCU_POWER_STATE>(void)
{
spinel_mcu_power_state_t state = SPINEL_MCU_POWER_STATE_ON;
switch (otPlatGetMcuPowerState(mInstance))
{
case OT_PLAT_MCU_POWER_STATE_ON:
state = SPINEL_MCU_POWER_STATE_ON;
break;
case OT_PLAT_MCU_POWER_STATE_LOW_POWER:
state = SPINEL_MCU_POWER_STATE_LOW_POWER;
break;
case OT_PLAT_MCU_POWER_STATE_OFF:
state = SPINEL_MCU_POWER_STATE_OFF;
break;
}
return mEncoder.WriteUint8(state);
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MCU_POWER_STATE>(void)
{
otError error = OT_ERROR_NONE;
otPlatMcuPowerState powerState;
uint8_t state;
SuccessOrExit(error = mDecoder.ReadUint8(state));
switch (state)
{
case SPINEL_MCU_POWER_STATE_ON:
powerState = OT_PLAT_MCU_POWER_STATE_ON;
break;
case SPINEL_MCU_POWER_STATE_LOW_POWER:
powerState = OT_PLAT_MCU_POWER_STATE_LOW_POWER;
break;
case SPINEL_MCU_POWER_STATE_OFF:
powerState = OT_PLAT_MCU_POWER_STATE_OFF;
break;
default:
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
SuccessOrExit(error = otPlatSetMcuPowerState(mInstance, powerState));
#if OPENTHREAD_FTD || OPENTHREAD_MTD
// If the call `otPlatSetMcuPowerState()` was successful and the desire
// state is `OFF`, ensure to disable Thread (MLE) operation (and stop
// legacy) and also bring the IPv6 interface down.
if (powerState == OT_PLAT_MCU_POWER_STATE_OFF)
{
if (otThreadGetDeviceRole(mInstance) != OT_DEVICE_ROLE_DISABLED)
{
IgnoreError(otThreadSetEnabled(mInstance, false));
}
if (otIp6IsEnabled(mInstance))
{
IgnoreError(otIp6SetEnabled(mInstance, false));
}
}
#endif // #if OPENTHREAD_FTD || OPENTHREAD_MTD
exit:
return error;
}
#else // OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MCU_POWER_STATE>(void)
{
return mEncoder.WriteUint8(SPINEL_MCU_POWER_STATE_ON);
}
#endif // OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_POWER_STATE>(void)
{
return mEncoder.WriteUint8(SPINEL_POWER_STATE_ONLINE);
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_POWER_STATE>(void) { return OT_ERROR_NOT_IMPLEMENTED; }
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_HWADDR>(void)
{
otExtAddress hwAddr;
otLinkGetFactoryAssignedIeeeEui64(mInstance, &hwAddr);
return mEncoder.WriteEui64(hwAddr);
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_LOCK>(void)
{
// TODO: Implement property lock (Needs API!)
return mEncoder.OverwriteWithLastStatusError(SPINEL_STATUS_UNIMPLEMENTED);
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_HOST_POWER_STATE>(void)
{
return mEncoder.WriteUint8(mHostPowerState);
}
// Setting `HOST_POWER_STATE` is treated and implemented differently from other
// handlers as it requires two special behaviors (a) the response frame for the
// set operation should be tracked and only when it is delivered we can assume
// that host is sleep (b) the response is critical so if there is no spinel
// buffer to prepare the response, the current spinel header is saved to
// prepare and send the response as soon as buffer space becomes available.
otError NcpBase::HandlePropertySet_SPINEL_PROP_HOST_POWER_STATE(uint8_t aHeader)
{
uint8_t powerState;
otError error = OT_ERROR_NONE;
error = mDecoder.ReadUint8(powerState);
if (error == OT_ERROR_NONE)
{
switch (powerState)
{
case SPINEL_HOST_POWER_STATE_OFFLINE:
case SPINEL_HOST_POWER_STATE_DEEP_SLEEP:
case SPINEL_HOST_POWER_STATE_LOW_POWER:
case SPINEL_HOST_POWER_STATE_ONLINE:
// Adopt the requested power state.
mHostPowerState = static_cast<spinel_host_power_state_t>(powerState);
break;
case SPINEL_HOST_POWER_STATE_RESERVED:
// Per the specification, treat this as synonymous with SPINEL_HOST_POWER_STATE_DEEP_SLEEP.
mHostPowerState = SPINEL_HOST_POWER_STATE_DEEP_SLEEP;
break;
default:
// Per the specification, treat unrecognized values as synonymous with SPINEL_HOST_POWER_STATE_LOW_POWER.
mHostPowerState = SPINEL_HOST_POWER_STATE_LOW_POWER;
break;
}
mHostPowerStateHeader = 0;
error = WritePropertyValueIsFrame(aHeader, SPINEL_PROP_HOST_POWER_STATE);
if (mHostPowerState != SPINEL_HOST_POWER_STATE_ONLINE)
{
if (error == OT_ERROR_NONE)
{
mHostPowerReplyFrameTag = GetLastOutboundFrameTag();
}
else
{
mHostPowerReplyFrameTag = Spinel::Buffer::kInvalidTag;
}
mHostPowerStateInProgress = true;
}
if (error != OT_ERROR_NONE)
{
mHostPowerStateHeader = aHeader;
// The reply will be queued when buffer space becomes available
// in the NCP tx buffer so we return `success` to avoid sending a
// NOMEM status for the same tid through `mDroppedReplyTid` list.
error = OT_ERROR_NONE;
}
}
else
{
error = WriteLastStatusFrame(aHeader, ThreadErrorToSpinelStatus(error));
}
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_UNSOL_UPDATE_LIST>(void)
{
otError error = OT_ERROR_NONE;
uint8_t numEntries;
const ChangedPropsSet::Entry *entry;
entry = mChangedPropsSet.GetSupportedEntries(numEntries);
for (uint8_t index = 0; index < numEntries; index++, entry++)
{
if (entry->mFilterable)
{
SuccessOrExit(error = mEncoder.WriteUintPacked(entry->mPropKey));
}
}
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_RSSI>(void)
{
return mEncoder.WriteInt8(otPlatRadioGetRssi(mInstance));
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_RX_SENSITIVITY>(void)
{
return mEncoder.WriteInt8(otPlatRadioGetReceiveSensitivity(mInstance));
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_FREQ>(void)
{
uint32_t freq_khz(0);
const uint8_t chan(otLinkGetChannel(mInstance));
if (chan == 0)
{
freq_khz = 868300;
}
else if (chan < 11)
{
freq_khz = 906000 - (2000 * 1) + 2000 * (chan);
}
else if (chan < 26)
{
freq_khz = 2405000 - (5000 * 11) + 5000 * (chan);
}
return mEncoder.WriteUint32(freq_khz);
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_CCA_THRESHOLD>(void)
{
int8_t threshold;
otError error = OT_ERROR_NONE;
error = otPlatRadioGetCcaEnergyDetectThreshold(mInstance, &threshold);
if (error == OT_ERROR_NONE)
{
error = mEncoder.WriteInt8(threshold);
}
else
{
error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
}
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_CCA_THRESHOLD>(void)
{
int8_t threshold = 0;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadInt8(threshold));
error = otPlatRadioSetCcaEnergyDetectThreshold(mInstance, threshold);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_TX_POWER>(void)
{
int8_t power;
otError error;
error = otPlatRadioGetTransmitPower(mInstance, &power);
if (error == OT_ERROR_NONE)
{
error = mEncoder.WriteInt8(power);
}
else
{
error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
}
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_TX_POWER>(void)
{
int8_t txPower = 0;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadInt8(txPower));
error = otPlatRadioSetTransmitPower(mInstance, txPower);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_FEM_LNA_GAIN>(void)
{
int8_t gain;
otError error = OT_ERROR_NONE;
error = otPlatRadioGetFemLnaGain(mInstance, &gain);
if (error == OT_ERROR_NONE)
{
error = mEncoder.WriteInt8(gain);
}
else
{
error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
}
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_FEM_LNA_GAIN>(void)
{
int8_t gain = 0;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadInt8(gain));
error = otPlatRadioSetFemLnaGain(mInstance, gain);
exit:
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_CHAN_MAX_POWER>(void)
{
uint8_t channel;
int8_t maxPower;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadUint8(channel));
SuccessOrExit(error = mDecoder.ReadInt8(maxPower));
error = otPlatRadioSetChannelMaxTransmitPower(mInstance, channel, maxPower);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_REGION_CODE>(void)
{
uint16_t regionCode;
otError error = OT_ERROR_NONE;
error = otPlatRadioGetRegion(mInstance, &regionCode);
if (error == OT_ERROR_NONE)
{
error = mEncoder.WriteUint16(regionCode);
}
else
{
error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
}
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_REGION_CODE>(void)
{
uint16_t regionCode;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadUint16(regionCode));
error = otPlatRadioSetRegion(mInstance, regionCode);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_DEBUG_TEST_ASSERT>(void)
{
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
OT_ASSERT(false);
#endif
// We only get to this point if `OT_ASSERT(false)`
// does not cause an NCP reset on the platform.
// In such a case we return `false` as the
// property value to indicate this.
OT_UNREACHABLE_CODE(return mEncoder.WriteBool(false);)
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_DEBUG_TEST_WATCHDOG>(void)
{
#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
while (true)
;
#endif
OT_UNREACHABLE_CODE(return OT_ERROR_NONE;)
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_DEBUG_NCP_LOG_LEVEL>(void)
{
return mEncoder.WriteUint8(ConvertLogLevel(otLoggingGetLevel()));
}
#if OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_DEBUG_NCP_LOG_LEVEL>(void)
{
otError error;
uint8_t spinelNcpLogLevel = 0;
otLogLevel logLevel;
SuccessOrExit(error = mDecoder.ReadUint8(spinelNcpLogLevel));
switch (spinelNcpLogLevel)
{
case SPINEL_NCP_LOG_LEVEL_EMERG:
case SPINEL_NCP_LOG_LEVEL_ALERT:
logLevel = OT_LOG_LEVEL_NONE;
break;
case SPINEL_NCP_LOG_LEVEL_CRIT:
logLevel = OT_LOG_LEVEL_CRIT;
break;
case SPINEL_NCP_LOG_LEVEL_ERR:
case SPINEL_NCP_LOG_LEVEL_WARN:
logLevel = OT_LOG_LEVEL_WARN;
break;
case SPINEL_NCP_LOG_LEVEL_NOTICE:
logLevel = OT_LOG_LEVEL_NOTE;
break;
case SPINEL_NCP_LOG_LEVEL_INFO:
logLevel = OT_LOG_LEVEL_INFO;
break;
case SPINEL_NCP_LOG_LEVEL_DEBUG:
logLevel = OT_LOG_LEVEL_DEBG;
break;
default:
ExitNow(error = OT_ERROR_INVALID_ARGS);
}
IgnoreError(otLoggingSetLevel(logLevel));
exit:
return error;
}
#endif // OPENTHREAD_CONFIG_LOG_LEVEL_DYNAMIC_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_DEBUG_LOG_TIMESTAMP_BASE>(void)
{
uint64_t timestampBase = 0;
otError error = OT_ERROR_NONE;
uint32_t currentTime = otPlatAlarmMilliGetNow();
SuccessOrExit(error = mDecoder.ReadUint64(timestampBase));
VerifyOrExit(timestampBase >= currentTime, error = OT_ERROR_INVALID_ARGS);
mLogTimestampBase = timestampBase - currentTime;
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_DEBUG_LOG_TIMESTAMP_BASE>(void)
{
return mEncoder.WriteUint64(mLogTimestampBase);
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_CHAN_SUPPORTED>(void)
{
#if OPENTHREAD_RADIO
return EncodeChannelMask(otPlatRadioGetSupportedChannelMask(mInstance));
#else
return EncodeChannelMask(otLinkGetSupportedChannelMask(mInstance));
#endif // OPENTHREAD_RADIO
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_PHY_CHAN_PREFERRED>(void)
{
return EncodeChannelMask(otPlatRadioGetPreferredChannelMask(mInstance));
}
#if OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RADIO_COEX_ENABLE>(void)
{
bool enabled;
otError error = OT_ERROR_NONE;
SuccessOrExit(error = mDecoder.ReadBool(enabled));
error = otPlatRadioSetCoexEnabled(mInstance, enabled);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RADIO_COEX_ENABLE>(void)
{
return mEncoder.WriteBool(otPlatRadioIsCoexEnabled(mInstance));
}
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_RADIO_COEX_METRICS>(void)
{
otRadioCoexMetrics coexMetrics;
otError error = otPlatRadioGetCoexMetrics(mInstance, &coexMetrics);
if (error != OT_ERROR_NONE)
{
error = mEncoder.OverwriteWithLastStatusError(ThreadErrorToSpinelStatus(error));
ExitNow();
}
// Encode Tx Request related metrics
SuccessOrExit(error = mEncoder.OpenStruct());
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxRequest));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxGrantImmediate));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxGrantWait));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxGrantWaitActivated));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxGrantWaitTimeout));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxGrantDeactivatedDuringRequest));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumTxDelayedGrant));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mAvgTxRequestToGrantTime));
SuccessOrExit(error = mEncoder.CloseStruct());
// Encode Rx Request related metrics
SuccessOrExit(error = mEncoder.OpenStruct());
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxRequest));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantImmediate));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantWait));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantWaitActivated));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantWaitTimeout));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantDeactivatedDuringRequest));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxDelayedGrant));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mAvgRxRequestToGrantTime));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumRxGrantNone));
SuccessOrExit(error = mEncoder.CloseStruct());
// Encode common metrics
SuccessOrExit(error = mEncoder.WriteBool(coexMetrics.mStopped));
SuccessOrExit(error = mEncoder.WriteUint32(coexMetrics.mNumGrantGlitch));
exit:
return error;
}
#endif
#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_INITIATOR_ENABLE || OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
otError NcpBase::DecodeLinkMetrics(otLinkMetrics *aMetrics, bool aAllowPduCount)
{
otError error = OT_ERROR_NONE;
uint8_t metrics = 0;
SuccessOrExit(error = mDecoder.ReadUint8(metrics));
if (metrics & SPINEL_THREAD_LINK_METRIC_PDU_COUNT)
{
VerifyOrExit(aAllowPduCount, error = OT_ERROR_INVALID_ARGS);
aMetrics->mPduCount = true;
}
if (metrics & SPINEL_THREAD_LINK_METRIC_LQI)
{
aMetrics->mLqi = true;
}
if (metrics & SPINEL_THREAD_LINK_METRIC_LINK_MARGIN)
{
aMetrics->mLinkMargin = true;
}
if (metrics & SPINEL_THREAD_LINK_METRIC_RSSI)
{
aMetrics->mRssi = true;
}
exit:
return error;
}
#endif
#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_CHAN_TARGET_POWER>(void)
{
otError error;
uint8_t channel;
int16_t targetPower;
SuccessOrExit(error = mDecoder.ReadUint8(channel));
SuccessOrExit(error = mDecoder.ReadInt16(targetPower));
error = otPlatRadioSetChannelTargetPower(mInstance, channel, targetPower);
exit:
return error;
}
template <> otError NcpBase::HandlePropertyInsert<SPINEL_PROP_PHY_CALIBRATED_POWER>(void)
{
otError error;
uint8_t channel;
int16_t actualPower;
const uint8_t *dataPtr;
uint16_t dataLen;
SuccessOrExit(error = mDecoder.ReadUint8(channel));
SuccessOrExit(error = mDecoder.ReadInt16(actualPower));
SuccessOrExit(error = mDecoder.ReadDataWithLen(dataPtr, dataLen));
error = otPlatRadioAddCalibratedPower(mInstance, channel, actualPower, dataPtr, dataLen);
exit:
return error;
}
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_PHY_CALIBRATED_POWER>(void)
{
return otPlatRadioClearCalibratedPowers(mInstance);
}
#endif // OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE
} // namespace Ncp
} // namespace ot
// ----------------------------------------------------------------------------
// MARK: Peek/Poke delegate API
// ----------------------------------------------------------------------------
#if OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
void otNcpRegisterPeekPoke(otNcpDelegateAllowPeekPoke aAllowPeekDelegate, otNcpDelegateAllowPeekPoke aAllowPokeDelegate)
{
ot::Ncp::NcpBase *ncp = ot::Ncp::NcpBase::GetNcpInstance();
if (ncp != nullptr)
{
ncp->RegisterPeekPokeDelegates(aAllowPeekDelegate, aAllowPokeDelegate);
}
}
#endif // OPENTHREAD_CONFIG_NCP_ENABLE_PEEK_POKE
// ----------------------------------------------------------------------------
// MARK: Virtual Datastream I/O (Public API)
// ----------------------------------------------------------------------------
otError otNcpStreamWrite(int aStreamId, const uint8_t *aDataPtr, int aDataLen)
{
otError error = OT_ERROR_INVALID_STATE;
ot::Ncp::NcpBase *ncp = ot::Ncp::NcpBase::GetNcpInstance();
if (ncp != nullptr)
{
error = ncp->StreamWrite(aStreamId, aDataPtr, aDataLen);
}
return error;
}
#if (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_APP)
extern "C" void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFormat, ...)
{
va_list args;
char logString[OPENTHREAD_CONFIG_NCP_SPINEL_LOG_MAX_SIZE];
ot::Ncp::NcpBase *ncp = ot::Ncp::NcpBase::GetNcpInstance();
va_start(args, aFormat);
if (vsnprintf(logString, sizeof(logString), aFormat, args) > 0)
{
if (ncp != nullptr)
{
ncp->Log(aLogLevel, aLogRegion, logString);
}
}
va_end(args);
}
#endif // (OPENTHREAD_CONFIG_LOG_OUTPUT == OPENTHREAD_CONFIG_LOG_OUTPUT_APP)