diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 638a80f28..1df2df537 100644 --- a/.github/workflows/unit.yml +++ b/.github/workflows/unit.yml @@ -82,6 +82,10 @@ jobs: run: ./script/cmake-build simulation - name: Test Simulation run: cd build/simulation && ninja test + - name: Build Multipan Simulation + run: ./script/cmake-build simulation -DOT_MULTIPAN_TEST=ON + - name: Test Multipan Simulation + run: cd build/simulation && ninja test - name: Build POSIX run: ./script/cmake-build posix - name: Test POSIX diff --git a/doc/ot_api_doc.h b/doc/ot_api_doc.h index d6ce51e47..84e48c800 100644 --- a/doc/ot_api_doc.h +++ b/doc/ot_api_doc.h @@ -176,6 +176,7 @@ * @defgroup plat-memory Memory * @defgroup plat-messagepool Message Pool * @defgroup plat-misc Miscellaneous + * @defgroup plat-multipan Multipan * @defgroup plat-otns Network Simulator * @defgroup plat-radio Radio * @defgroup plat-settings Settings diff --git a/etc/cmake/options.cmake b/etc/cmake/options.cmake index 5f5d96863..3b5a155d3 100644 --- a/etc/cmake/options.cmake +++ b/etc/cmake/options.cmake @@ -246,7 +246,7 @@ ot_option(OT_UDP_FORWARD OPENTHREAD_CONFIG_UDP_FORWARD_ENABLE "UDP forward") ot_option(OT_UPTIME OPENTHREAD_CONFIG_UPTIME_ENABLE "uptime") option(OT_DOC "build OpenThread documentation") - +option(OT_MULTIPAN_RCP "Multi-PAN RCP" OFF) message(STATUS "- - - - - - - - - - - - - - - - ") # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/examples/apps/ncp/main.c b/examples/apps/ncp/main.c index ec3f8dc96..5f59261e8 100644 --- a/examples/apps/ncp/main.c +++ b/examples/apps/ncp/main.c @@ -42,6 +42,16 @@ #include "openthread-system.h" #include "lib/platform/reset_util.h" + +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +#if OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE == 0 +#error "Support for multiple OpenThread static instance is disabled." +#endif +#define ENDPOINT_CT OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM +#else +#define ENDPOINT_CT 1 +#endif /* OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE */ + /** * Initializes the NCP app. * @@ -49,6 +59,7 @@ * */ extern void otAppNcpInit(otInstance *aInstance); +extern void otAppNcpInitMulti(otInstance **aInstances, uint8_t count); #if OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE OT_TOOL_WEAK void *otPlatCAlloc(size_t aNum, size_t aSize) { return calloc(aNum, aSize); } @@ -70,7 +81,9 @@ int main(int argc, char *argv[]) prctl(PR_SET_PDEATHSIG, SIGHUP); #endif -#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + otInstance *instances[ENDPOINT_CT] = {NULL}; +#elif OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE size_t otInstanceBufferLength = 0; uint8_t *otInstanceBuffer = NULL; #endif @@ -79,7 +92,16 @@ pseudo_reset: otSysInit(argc, argv); -#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + for (int i = 0; i < ENDPOINT_CT; i++) + { + instances[i] = otInstanceInitMultiple(i); + + assert(instances[i]); + } + instance = instances[0]; +#elif OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE + // Call to query the buffer size (void)otInstanceInit(NULL, &otInstanceBufferLength); @@ -94,7 +116,11 @@ pseudo_reset: #endif assert(instance); +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + otAppNcpInitMulti(instances, ENDPOINT_CT); +#else otAppNcpInit(instance); +#endif while (!otSysPseudoResetWasRequested()) { @@ -103,7 +129,7 @@ pseudo_reset: } otInstanceFinalize(instance); -#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE +#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && !OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE free(otInstanceBuffer); #endif diff --git a/examples/apps/ncp/ncp.c b/examples/apps/ncp/ncp.c index df6529a3a..38c95b42d 100644 --- a/examples/apps/ncp/ncp.c +++ b/examples/apps/ncp/ncp.c @@ -61,4 +61,17 @@ void otAppNcpInit(otInstance *aInstance) otNcpHdlcInit(aInstance, NcpSend); #endif } + +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +void otAppNcpInitMulti(otInstance **aInstances, uint8_t aCount) +{ +#if OPENTHREAD_CONFIG_NCP_SPI_ENABLE +#error Multipan support not implemented for SPI +#else + IgnoreError(otPlatUartEnable()); + + otNcpHdlcInitMulti(aInstances, aCount, NcpSend); +#endif +} +#endif #endif // !OPENTHREAD_ENABLE_NCP_VENDOR_HOOK diff --git a/examples/platforms/simulation/CMakeLists.txt b/examples/platforms/simulation/CMakeLists.txt index c99597d4a..439fcb9d4 100644 --- a/examples/platforms/simulation/CMakeLists.txt +++ b/examples/platforms/simulation/CMakeLists.txt @@ -68,6 +68,7 @@ add_library(openthread-simulation infra_if.c logging.c misc.c + multipan.c radio.c spi-stubs.c system.c diff --git a/examples/platforms/simulation/multipan.c b/examples/platforms/simulation/multipan.c new file mode 100644 index 000000000..1c173dfe0 --- /dev/null +++ b/examples/platforms/simulation/multipan.c @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023, 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 "platform-simulation.h" + +#include +#include + +#include + +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +static otInstance *sActiveInstance; +#endif + +otError otPlatMultipanGetActiveInstance(otInstance **aInstance) +{ + otError error = OT_ERROR_NOT_IMPLEMENTED; + OT_UNUSED_VARIABLE(aInstance); + +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + *aInstance = sActiveInstance; + error = OT_ERROR_NONE; +#endif + + return error; +} + +otError otPlatMultipanSetActiveInstance(otInstance *aInstance, bool aCompletePending) +{ + otError error = OT_ERROR_NOT_IMPLEMENTED; + + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aCompletePending); + +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + sActiveInstance = aInstance; + error = OT_ERROR_NONE; +#endif + + return error; +} diff --git a/include/openthread/BUILD.gn b/include/openthread/BUILD.gn index 5bc1c0967..e4760c29c 100644 --- a/include/openthread/BUILD.gn +++ b/include/openthread/BUILD.gn @@ -97,6 +97,7 @@ source_set("openthread") { "platform/memory.h", "platform/messagepool.h", "platform/misc.h", + "platform/multipan.h", "platform/otns.h", "platform/radio.h", "platform/settings.h", diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 18e23d2e6..7094bf408 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -53,7 +53,7 @@ extern "C" { * @note This number versions both OpenThread platform and user APIs. * */ -#define OPENTHREAD_API_VERSION (376) +#define OPENTHREAD_API_VERSION (377) /** * @addtogroup api-instance @@ -102,6 +102,23 @@ otInstance *otInstanceInit(void *aInstanceBuffer, size_t *aInstanceBufferSize); */ otInstance *otInstanceInitSingle(void); +/** + * Initializes the OpenThread instance. + * + * This function initializes OpenThread and prepares it for subsequent OpenThread API calls. This function must be + * called before any other calls to OpenThread. This method utilizes static buffer to initialize the OpenThread + * instance. + * + * This function is available and can only be used when support for multiple OpenThread static instances is + * enabled (`OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE`) + * + * @param[in] aIdx The index of the OpenThread instance to initialize. + * + * @returns A pointer to the new OpenThread instance. + * + */ +otInstance *otInstanceInitMultiple(uint8_t aIdx); + /** * Gets the instance identifier. * diff --git a/include/openthread/ncp.h b/include/openthread/ncp.h index 7e07fa290..8c810fc84 100644 --- a/include/openthread/ncp.h +++ b/include/openthread/ncp.h @@ -89,6 +89,16 @@ void otNcpHdlcReceive(const uint8_t *aBuf, uint16_t aBufLength); */ void otNcpHdlcInit(otInstance *aInstance, otNcpHdlcSendCallback aSendCallback); +/** + * Initialize the NCP based on HDLC framing. + * + * @param[in] aInstances The OpenThread instance pointers array. + * @param[in] aCount Number of elements in the array. + * @param[in] aSendCallback The function pointer used to send NCP data. + * + */ +void otNcpHdlcInitMulti(otInstance **aInstance, uint8_t aCount, otNcpHdlcSendCallback aSendCallback); + /** * Initialize the NCP based on SPI framing. * diff --git a/include/openthread/platform/multipan.h b/include/openthread/platform/multipan.h new file mode 100644 index 000000000..69cd9839c --- /dev/null +++ b/include/openthread/platform/multipan.h @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2023, 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 + * @brief + * This file defines the multipan interface for OpenThread. + * + * Multipan RCP is a feature that allows single RCP operate in multiple networks. + * + * Currently we support two types of multipan RCP: + * - Full multipan: RCP operates in parallel on both networks (for example using more than one transceiver) + * - Switching RCP: RCP can communicate only with one network at a time and requires network switching mechanism. + * Switching can be automatic (for example time based, radio sleep based) or manually contolled by + * the host. + * + * Full multipan RCP and Automatic Switching RCP do not require any special care from the host side. + * Manual Switching RCP requires host to switch currently active network. + * + */ + +#ifndef OPENTHREAD_PLATFORM_MULTIPAN_H_ +#define OPENTHREAD_PLATFORM_MULTIPAN_H_ + +#include + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @addtogroup plat-multipan + * + * @brief + * This module includes the platform abstraction for multipan support. + * + * @{ + * + */ + +/** + * Get instance currently in control of the radio. + * + * If radio does not operate in parallel on all interfaces, this function returns an instance object with granted + * radio access. + * + * @param[out] aInstance Pointer to the variable for storing the active instance pointer. + * + * @retval OT_ERROR_NONE Successfully retrieved the property. + * @retval OT_ERROR_NOT_IMPLEMENTED Failed due to lack of the support in radio. + * @retval OT_ERROR_INVALID_COMMAND Platform supports all interfaces simultaneously. + * + */ +otError otPlatMultipanGetActiveInstance(otInstance **aInstance); + +/** + * Set `aInstance` as the current active instance controlling radio. + * + * This function allows selecting the currently active instance on platforms that do not support parallel + * communication on multiple interfaces. In other words, if more than one instance is in a receive state, calling + * #otPlatMultipanSetActiveInstance guarantees that specified instance will be the one receiving. This function returns + * if the request was received properly. After interface switching is complete, the platform should call + * #otPlatMultipanSwitchoverDone. Switching interfaces may take longer if `aCompletePending` is set true. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aCompletePending True if ongoing radio operation should complete before interface switch (Soft switch), + * false for force switch. + * + * @retval OT_ERROR_NONE Successfully set the property. + * @retval OT_ERROR_BUSY Failed due to another operation ongoing. + * @retval OT_ERROR_NOT_IMPLEMENTED Failed due to unknown instance or more instances than interfaces available. + * @retval OT_ERROR_INVALID_COMMAND Platform supports all interfaces simultaneously. + * @retval OT_ERROR_ALREADY Given interface is already active. + * + */ +otError otPlatMultipanSetActiveInstance(otInstance *aInstance, bool aCompletePending); + +/** + * The platform completed the interface switching procedure. + * + * Should be invoked immediately after processing #otPlatMultipanSetActiveInstance if no delay is needed, or if + * some longer radio operations need to complete first, after the switch in interfaces is fully complete. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aSuccess True if successfully switched the interfaces, false if switching failed. + * + */ +extern void otPlatMultipanSwitchoverDone(otInstance *aInstance, bool aSuccess); + +/** + * Get the instance pointer corresponding to the given IID. + * + * @param[in] aIid The IID of the interface. + * + * @retval Instance pointer if aIid is has an instance assigned, nullptr otherwise. + */ +otInstance *otPlatMultipanIidToInstance(uint8_t aIid); + +/** + * Get the IID corresponding to the given OpenThread instance pointer. + * + * @param[in] aInstance The OpenThread instance structure. + * + * @retval IID of the given instance, broadcast IID otherwise. + */ +uint8_t otPlatMultipanInstanceToIid(otInstance *aInstance); + +/** + * @} + * + */ + +#ifdef __cplusplus +} // end of extern "C" +#endif + +#endif // OPENTHREAD_PLATFORM_MULTIPAN_H_ diff --git a/src/core/api/instance_api.cpp b/src/core/api/instance_api.cpp index 6d51d3c39..5d6d5971e 100644 --- a/src/core/api/instance_api.cpp +++ b/src/core/api/instance_api.cpp @@ -58,6 +58,16 @@ using namespace ot; #if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE +#if OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE +otInstance *otInstanceInitMultiple(uint8_t aIdx) +{ + Instance *instance; + + instance = Instance::InitMultiple(aIdx); + + return instance; +} +#endif // OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE otInstance *otInstanceInit(void *aInstanceBuffer, size_t *aInstanceBufferSize) { Instance *instance; diff --git a/src/core/config/misc.h b/src/core/config/misc.h index df9fd28fa..c9be7ec5e 100644 --- a/src/core/config/misc.h +++ b/src/core/config/misc.h @@ -141,6 +141,16 @@ #define OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + * + * Define to 1 to enable multipan RCP support. + * + */ +#ifndef OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +#define OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE 0 +#endif + /** * @def OPENTHREAD_CONFIG_REFERENCE_DEVICE_ENABLE * @@ -520,6 +530,26 @@ #define OPENTHREAD_CONFIG_NEIGHBOR_DISCOVERY_AGENT_ENABLE 0 #endif +/** + * @def OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE + * + * Define to 1 to enable multiple static instance support. + * + */ +#ifndef OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE +#define OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE 0 +#endif + +/** + * @def OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM + * + * Define number of OpenThread instance for static allocation buffer. + * + */ +#ifndef OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM +#define OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM 3 +#endif + /** * @def OPENTHREAD_CONFIG_ALLOW_EMPTY_NETWORK_NAME * diff --git a/src/core/instance/instance.cpp b/src/core/instance/instance.cpp index 872718faf..706671a97 100644 --- a/src/core/instance/instance.cpp +++ b/src/core/instance/instance.cpp @@ -48,6 +48,16 @@ OT_DEFINE_ALIGNED_VAR(gInstanceRaw, sizeof(Instance), uint64_t); #endif +#if OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE + +#define INSTANCE_SIZE_ALIGNED OT_ALIGNED_VAR_SIZE(sizeof(ot::Instance), uint64_t) +#define MULTI_INSTANCE_SIZE (OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM * INSTANCE_SIZE_ALIGNED) + +// Define the raw storage used for OpenThread instance (in multi-instance case). +static uint64_t gMultiInstanceRaw[MULTI_INSTANCE_SIZE]; + +#endif + #if OPENTHREAD_MTD || OPENTHREAD_FTD #if !OPENTHREAD_CONFIG_HEAP_EXTERNAL_ENABLE OT_DEFINE_ALIGNED_VAR(sHeapRaw, sizeof(Utils::Heap), uint64_t); @@ -288,6 +298,25 @@ Instance &Instance::Get(void) } #else // #if !OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE +#if OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE + +Instance *Instance::InitMultiple(uint8_t aIdx) +{ + size_t bufferSize; + uint64_t *instanceBuffer = gMultiInstanceRaw + aIdx * INSTANCE_SIZE_ALIGNED; + Instance *instance = reinterpret_cast(instanceBuffer); + + VerifyOrExit(aIdx < OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + VerifyOrExit(!instance->mIsInitialized); + + bufferSize = (&gMultiInstanceRaw[MULTI_INSTANCE_SIZE] - instanceBuffer) * sizeof(uint64_t); + instance = Instance::Init(instanceBuffer, &bufferSize); + +exit: + return instance; +} + +#endif // #if OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE Instance *Instance::Init(void *aBuffer, size_t *aBufferSize) { diff --git a/src/core/instance/instance.hpp b/src/core/instance/instance.hpp index 4d1a3255b..596a5c68c 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -186,6 +186,21 @@ public: */ static Instance *Init(void *aBuffer, size_t *aBufferSize); +#if OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE + /** + * This static method initializes the OpenThread instance. + * + * This method utilizes static buffer to initialize the OpenThread instance. + * This function must be called before any other calls on OpenThread instance. + * + * @param[in] aIdx The index of the OpenThread instance to initialize. + * + * @returns A pointer to the new OpenThread instance. + * + */ + static Instance *InitMultiple(uint8_t aIdx); +#endif + #else // OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE /** diff --git a/src/lib/spinel/openthread-spinel-config.h b/src/lib/spinel/openthread-spinel-config.h index 086a75842..6b3316ac1 100644 --- a/src/lib/spinel/openthread-spinel-config.h +++ b/src/lib/spinel/openthread-spinel-config.h @@ -78,4 +78,15 @@ #ifndef OPENTHREAD_SPINEL_CONFIG_RCP_TIME_SYNC_INTERVAL #define OPENTHREAD_SPINEL_CONFIG_RCP_TIME_SYNC_INTERVAL (60 * 1000 * 1000) #endif + +/** + * @def OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID + * + * Define broadcast IID for spinel frames dedicated to all hosts in multipan configuration. + * + */ +#ifndef OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID +#define OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID SPINEL_HEADER_IID_3 +#endif + #endif // OPENTHREAD_SPINEL_CONFIG_H_ diff --git a/src/lib/spinel/radio_spinel.cpp b/src/lib/spinel/radio_spinel.cpp index ffca5b277..2cd1deaff 100644 --- a/src/lib/spinel/radio_spinel.cpp +++ b/src/lib/spinel/radio_spinel.cpp @@ -51,6 +51,35 @@ namespace ot { namespace Spinel { +char RadioSpinel::sVersion[kVersionStringSize] = ""; + +otExtAddress RadioSpinel::sIeeeEui64; + +bool RadioSpinel::sIsReady = false; ///< NCP ready. + +bool RadioSpinel::sSupportsLogStream = + false; ///< RCP supports `LOG_STREAM` property with OpenThread log meta-data format. + +bool RadioSpinel::sSupportsResetToBootloader = false; ///< RCP supports resetting into bootloader mode. + +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) : mInstance(nullptr) , mSpinelInterface(nullptr) @@ -62,21 +91,19 @@ RadioSpinel::RadioSpinel(void) , mPropertyFormat(nullptr) , mExpectedCommand(0) , mError(OT_ERROR_NONE) + , mIid(SPINEL_HEADER_INVALID_IID) , mTransmitFrame(nullptr) , mShortAddress(0) , mPanId(0xffff) - , mRadioCaps(0) , mChannel(0) , mRxSensitivity(0) , mState(kStateDisabled) , mIsPromiscuous(false) , mRxOnWhenIdle(true) - , mIsReady(false) - , mSupportsLogStream(false) - , mSupportsResetToBootloader(false) , mIsTimeSynced(false) #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 , mRcpFailureCount(0) + , mRcpFailure(kRcpFailureNone) , mSrcMatchShortEntryCount(0) , mSrcMatchExtEntryCount(0) , mMacKeySet(false) @@ -84,7 +111,6 @@ RadioSpinel::RadioSpinel(void) , mTransmitPowerSet(false) , mCoexEnabledSet(false) , mFemLnaGainSet(false) - , mRcpFailed(false) , mEnergyScanning(false) , mMacFrameCounterSet(false) #endif @@ -97,11 +123,16 @@ RadioSpinel::RadioSpinel(void) , mRadioTimeRecalcStart(UINT64_MAX) , mRadioTimeOffset(UINT64_MAX) { - mVersion[0] = '\0'; + memset(mIidList, SPINEL_HEADER_INVALID_IID, sizeof(mIidList)); memset(&mRadioSpinelMetrics, 0, sizeof(mRadioSpinelMetrics)); + memset(&mCallbacks, 0, sizeof(mCallbacks)); } -void RadioSpinel::Init(SpinelInterface &aSpinelInterface, bool aResetRadio, bool aSkipRcpCompatibilityCheck) +void RadioSpinel::Init(SpinelInterface &aSpinelInterface, + bool aResetRadio, + bool aSkipRcpCompatibilityCheck, + const spinel_iid_t *aIidList, + uint8_t aIidListLength) { otError error = OT_ERROR_NONE; bool supportsRcpApiVersion; @@ -114,10 +145,16 @@ void RadioSpinel::Init(SpinelInterface &aSpinelInterface, bool aResetRadio, bool mSpinelInterface = &aSpinelInterface; SuccessOrDie(mSpinelInterface->Init(HandleReceivedFrame, this, mRxFrameBuffer)); + 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)); + ResetRcp(aResetRadio); SuccessOrExit(error = CheckSpinelVersion()); - SuccessOrExit(error = Get(SPINEL_PROP_NCP_VERSION, SPINEL_DATATYPE_UTF8_S, mVersion, sizeof(mVersion))); - SuccessOrExit(error = Get(SPINEL_PROP_HWADDR, SPINEL_DATATYPE_EUI64_S, mIeeeEui64.m8)); + 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); @@ -135,16 +172,34 @@ exit: SuccessOrDie(error); } +void RadioSpinel::SetCallbacks(const struct RadioSpinelCallbacks &aCallbacks) +{ +#if OPENTHREAD_CONFIG_DIAG_ENABLE + assert(aCallbacks.mDiagReceiveDone != nullptr); + assert(aCallbacks.mDiagTransmitDone != nullptr); +#endif + assert(aCallbacks.mEnergyScanDone != nullptr); + assert(aCallbacks.mReceiveDone != nullptr); + assert(aCallbacks.mTransmitDone != nullptr); + assert(aCallbacks.mTxStarted != nullptr); + + mCallbacks = aCallbacks; +} + void RadioSpinel::ResetRcp(bool aResetRadio) { bool hardwareReset; bool resetDone = false; - mIsReady = 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) && (WaitResponse(false) == OT_ERROR_NONE)) + 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); } @@ -230,7 +285,7 @@ bool RadioSpinel::IsRcp(bool &aSupportsRcpApiVersion, bool &aSupportsRcpMinHostA if (capability == SPINEL_CAP_OPENTHREAD_LOG_METADATA) { - mSupportsLogStream = true; + sSupportsLogStream = true; } if (capability == SPINEL_CAP_RCP_API_VERSION) @@ -240,7 +295,7 @@ bool RadioSpinel::IsRcp(bool &aSupportsRcpApiVersion, bool &aSupportsRcpMinHostA if (capability == SPINEL_CAP_RCP_RESET_TO_BOOTLOADER) { - mSupportsResetToBootloader = true; + sSupportsResetToBootloader = true; } if (capability == SPINEL_PROP_RCP_MIN_HOST_API_VERSION) @@ -273,11 +328,11 @@ otError RadioSpinel::CheckRadioCapabilities(void) unsigned int radioCaps; SuccessOrExit(error = Get(SPINEL_PROP_RADIO_CAPS, SPINEL_DATATYPE_UINT_PACKED_S, &radioCaps)); - mRadioCaps = static_cast(radioCaps); + sRadioCaps = static_cast(radioCaps); - if ((mRadioCaps & kRequiredRadioCaps) != kRequiredRadioCaps) + if ((sRadioCaps & kRequiredRadioCaps) != kRequiredRadioCaps) { - otRadioCaps missingCaps = (mRadioCaps & kRequiredRadioCaps) ^ kRequiredRadioCaps; + otRadioCaps missingCaps = (sRadioCaps & kRequiredRadioCaps) ^ kRequiredRadioCaps; // missingCaps may be an unused variable when LogCrit is blank // avoid compiler warning in that case @@ -355,6 +410,7 @@ void RadioSpinel::Deinit(void) } // This allows implementing pseudo reset. + sIsReady = false; new (this) RadioSpinel(); } @@ -369,9 +425,16 @@ void RadioSpinel::HandleReceivedFrame(void) LogSpinelFrame(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), false); unpacked = spinel_datatype_unpack(mRxFrameBuffer.GetFrame(), mRxFrameBuffer.GetLength(), "C", &header); - VerifyOrExit(unpacked > 0 && (header & SPINEL_HEADER_FLAG) == SPINEL_HEADER_FLAG && - SPINEL_HEADER_GET_IID(header) == 0, - error = OT_ERROR_PARSE); + // 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) { @@ -604,7 +667,14 @@ void RadioSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, } LogInfo("RCP reset: %s", spinel_status_to_cstr(status)); - mIsReady = true; + sIsReady = true; + } + else if (status == SPINEL_STATUS_SWITCHOVER_DONE || status == SPINEL_STATUS_SWITCHOVER_FAILED) + { + if (mCallbacks.mSwitchoverDone != nullptr) + { + mCallbacks.mSwitchoverDone(mInstance, status == SPINEL_STATUS_SWITCHOVER_DONE); + } } else { @@ -624,7 +694,7 @@ void RadioSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, mEnergyScanning = false; #endif - otPlatRadioEnergyScanDone(mInstance, maxRssi); + mCallbacks.mEnergyScanDone(mInstance, maxRssi); } else if (aKey == SPINEL_PROP_STREAM_DEBUG) { @@ -637,7 +707,7 @@ void RadioSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, logStream[len] = '\0'; LogDebg("RCP => %s", logStream); } - else if ((aKey == SPINEL_PROP_STREAM_LOG) && mSupportsLogStream) + else if ((aKey == SPINEL_PROP_STREAM_LOG) && sSupportsLogStream) { const char *logString; uint8_t logLevel; @@ -719,7 +789,7 @@ otError RadioSpinel::ParseRadioFrame(otRadioFrame &aFrame, aBuffer += unpacked; aLength -= static_cast(unpacked); - if (mRadioCaps & OT_RADIO_CAPS_TRANSMIT_SEC) + if (sRadioCaps & OT_RADIO_CAPS_TRANSMIT_SEC) { unpacked = spinel_datatype_unpack_in_place(aBuffer, aLength, @@ -796,14 +866,13 @@ void RadioSpinel::RadioReceive(void) #if OPENTHREAD_CONFIG_DIAG_ENABLE if (otPlatDiagModeGet()) { - otPlatDiagRadioReceiveDone(mInstance, &mRxRadioFrame, OT_ERROR_NONE); + mCallbacks.mDiagReceiveDone(mInstance, &mRxRadioFrame, OT_ERROR_NONE); } else #endif { - otPlatRadioReceiveDone(mInstance, &mRxRadioFrame, OT_ERROR_NONE); + mCallbacks.mReceiveDone(mInstance, &mRxRadioFrame, OT_ERROR_NONE); } - exit: return; } @@ -813,12 +882,12 @@ void RadioSpinel::TransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, ot #if OPENTHREAD_CONFIG_DIAG_ENABLE if (otPlatDiagModeGet()) { - otPlatDiagRadioTransmitDone(mInstance, aFrame, aError); + mCallbacks.mDiagTransmitDone(mInstance, aFrame, aError); } else #endif { - otPlatRadioTxDone(mInstance, aFrame, aAckFrame, aError); + mCallbacks.mTransmitDone(mInstance, aFrame, aAckFrame, aError); } } @@ -991,7 +1060,7 @@ exit: otError RadioSpinel::GetIeeeEui64(uint8_t *aIeeeEui64) { - memcpy(aIeeeEui64, mIeeeEui64.m8, sizeof(mIeeeEui64.m8)); + memcpy(aIeeeEui64, sIeeeEui64.m8, sizeof(sIeeeEui64.m8)); return OT_ERROR_NONE; } @@ -1296,7 +1365,7 @@ otError RadioSpinel::EnergyScan(uint8_t aScanChannel, uint16_t aScanDuration) { otError error; - VerifyOrExit(mRadioCaps & OT_RADIO_CAPS_ENERGY_SCAN, error = OT_ERROR_NOT_CAPABLE); + VerifyOrExit(sRadioCaps & OT_RADIO_CAPS_ENERGY_SCAN, error = OT_ERROR_NOT_CAPABLE); #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 mScanChannel = aScanChannel; @@ -1329,7 +1398,7 @@ otError RadioSpinel::Get(spinel_prop_key_t aKey, const char *aFormat, ...) error = RequestWithPropertyFormatV(aFormat, SPINEL_CMD_PROP_VALUE_GET, aKey, nullptr, mPropertyArgs); va_end(mPropertyArgs); #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 - } while (mRcpFailed); + } while (mRcpFailure != kRcpFailureNone); #endif return error; @@ -1356,7 +1425,7 @@ otError RadioSpinel::GetWithParam(spinel_prop_key_t aKey, aParamSize); va_end(mPropertyArgs); #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 - } while (mRcpFailed); + } while (mRcpFailure != kRcpFailureNone); #endif return error; @@ -1378,7 +1447,7 @@ otError RadioSpinel::Set(spinel_prop_key_t aKey, const char *aFormat, ...) mPropertyArgs); va_end(mPropertyArgs); #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 - } while (mRcpFailed); + } while (mRcpFailure != kRcpFailureNone); #endif return error; @@ -1400,7 +1469,7 @@ otError RadioSpinel::Insert(spinel_prop_key_t aKey, const char *aFormat, ...) mPropertyArgs); va_end(mPropertyArgs); #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 - } while (mRcpFailed); + } while (mRcpFailure != kRcpFailureNone); #endif return error; @@ -1422,7 +1491,7 @@ otError RadioSpinel::Remove(spinel_prop_key_t aKey, const char *aFormat, ...) mPropertyArgs); va_end(mPropertyArgs); #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 - } while (mRcpFailed); + } while (mRcpFailure != kRcpFailureNone); #endif return error; @@ -1448,7 +1517,7 @@ otError RadioSpinel::WaitResponse(bool aHandleRcpTimeout) } ExitNow(mError = OT_ERROR_RESPONSE_TIMEOUT); } - } while (mWaitingTid || !mIsReady); + } while (mWaitingTid || !sIsReady); LogIfFail("Error waiting response", mError); // This indicates end of waiting response. @@ -1488,14 +1557,14 @@ otError RadioSpinel::SendReset(uint8_t aResetType) uint8_t buffer[kMaxSpinelFrame]; spinel_ssize_t packed; - if ((aResetType == SPINEL_RESET_BOOTLOADER) && !mSupportsResetToBootloader) + 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_0, SPINEL_CMD_RESET, aResetType); + SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid), SPINEL_CMD_RESET, aResetType); VerifyOrExit(packed > 0 && static_cast(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); @@ -1518,7 +1587,7 @@ otError RadioSpinel::SendCommand(uint32_t aCommand, uint16_t offset; // Pack the header, command and key - packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0 | tid, + packed = spinel_datatype_pack(buffer, sizeof(buffer), "Cii", SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(mIid) | tid, aCommand, aKey); VerifyOrExit(packed > 0 && static_cast(packed) <= sizeof(buffer), error = OT_ERROR_NO_BUFS); @@ -1668,7 +1737,7 @@ void RadioSpinel::HandleTransmitDone(uint32_t aCommand, static_cast(mTransmitFrame)->SetIsHeaderUpdated(headerUpdated); - if ((mRadioCaps & OT_RADIO_CAPS_TRANSMIT_SEC) && headerUpdated && + if ((sRadioCaps & OT_RADIO_CAPS_TRANSMIT_SEC) && headerUpdated && static_cast(mTransmitFrame)->GetSecurityEnabled()) { uint8_t keyId; @@ -1698,12 +1767,12 @@ otError RadioSpinel::Transmit(otRadioFrame &aFrame) { otError error = OT_ERROR_INVALID_STATE; - VerifyOrExit(mState == kStateReceive || (mState == kStateSleep && (mRadioCaps & OT_RADIO_CAPS_SLEEP_TO_TX))); + VerifyOrExit(mState == kStateReceive || (mState == kStateSleep && (sRadioCaps & OT_RADIO_CAPS_SLEEP_TO_TX))); mTransmitFrame = &aFrame; // `otPlatRadioTxStarted()` is triggered immediately for now, which may be earlier than real started time. - otPlatRadioTxStarted(mInstance, mTransmitFrame); + mCallbacks.mTxStarted(mInstance, mTransmitFrame); error = Request(SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_STREAM_RAW, SPINEL_DATATYPE_DATA_WLEN_S // Frame data @@ -1965,7 +2034,7 @@ void RadioSpinel::HandleRcpUnexpectedReset(spinel_status_t aStatus) LogCrit("Unexpected RCP reset: %s", spinel_status_to_cstr(aStatus)); #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 - mRcpFailed = true; + mRcpFailure = kRcpFailureUnexpectedReset; #elif OPENTHREAD_SPINEL_CONFIG_ABORT_ON_UNEXPECTED_RCP_RESET_ENABLE abort(); #else @@ -1978,9 +2047,9 @@ void RadioSpinel::HandleRcpTimeout(void) mRadioSpinelMetrics.mRcpTimeoutCount++; #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 - mRcpFailed = true; + mRcpFailure = kRcpFailureTimeout; #else - if (!mIsReady) + 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:"); @@ -1997,12 +2066,18 @@ void RadioSpinel::RecoverFromRcpFailure(void) #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 constexpr int16_t kMaxFailureCount = OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT; State recoveringState = mState; + bool skipReset = false; - if (!mRcpFailed) + if (mRcpFailure == kRcpFailureNone) { ExitNow(); } - mRcpFailed = false; + +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + skipReset = (mRcpFailure == kRcpFailureUnexpectedReset); +#endif + + mRcpFailure = kRcpFailureNone; LogWarn("RCP failure detected"); @@ -2025,7 +2100,15 @@ void RadioSpinel::RecoverFromRcpFailure(void) mError = OT_ERROR_NONE; mIsTimeSynced = false; - ResetRcp(mResetRadioOnStartup); + if (skipReset) + { + sIsReady = true; + } + else + { + ResetRcp(mResetRadioOnStartup); + } + SuccessOrDie(Set(SPINEL_PROP_PHY_ENABLED, SPINEL_DATATYPE_BOOL_S, true)); mState = kStateSleep; @@ -2148,7 +2231,7 @@ void RadioSpinel::RestoreProperties(void) } #endif // OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE - if ((mRadioCaps & OT_RADIO_CAPS_RX_ON_WHEN_IDLE) != 0) + if ((sRadioCaps & OT_RADIO_CAPS_RX_ON_WHEN_IDLE) != 0) { SuccessOrDie(Set(SPINEL_PROP_MAC_RX_ON_WHEN_IDLE_MODE, SPINEL_DATATYPE_BOOL_S, mRxOnWhenIdle)); } @@ -2157,6 +2240,32 @@ void RadioSpinel::RestoreProperties(void) } #endif // OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 +otError RadioSpinel::GetMultipanActiveInterface(spinel_iid_t *aIid) +{ + otError error = Get(SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE, SPINEL_DATATYPE_UINT8_S, aIid); + LogIfFail("Get GetMultipanActiveInterface failed", error); + return error; +} + +otError RadioSpinel::SetMultipanActiveInterface(spinel_iid_t aIid, bool aCompletePending) +{ + otError error; + uint8_t value; + + VerifyOrExit(aIid == (aIid & SPINEL_MULTIPAN_INTERFACE_ID_MASK), error = OT_ERROR_INVALID_ARGS); + + value = static_cast(aIid); + if (aCompletePending) + { + value |= (1 << SPINEL_MULTIPAN_INTERFACE_SOFT_SWITCH_SHIFT); + } + + error = Set(SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE, SPINEL_DATATYPE_UINT8_S, value); + +exit: + return error; +} + otError RadioSpinel::SetChannelMaxTransmitPower(uint8_t aChannel, int8_t aMaxPower) { otError error = OT_ERROR_NONE; @@ -2320,8 +2429,9 @@ void RadioSpinel::LogSpinelFrame(const uint8_t *aFrame, uint16_t aLength, bool a unpacked = spinel_datatype_unpack(aFrame, aLength, "CiiD", &header, &cmd, &key, &data, &len); VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); - start += Snprintf(start, static_cast(end - start), "%s, flg:0x%x, tid:%u, cmd:%s", prefix, - SPINEL_HEADER_GET_FLAG(header), SPINEL_HEADER_GET_TID(header), spinel_command_to_cstr(cmd)); + start += Snprintf(start, static_cast(end - start), "%s, flg:0x%x, iid:%d, tid:%u, cmd:%s", prefix, + SPINEL_HEADER_GET_FLAG(header), SPINEL_HEADER_GET_IID(header), SPINEL_HEADER_GET_TID(header), + spinel_command_to_cstr(cmd)); VerifyOrExit(cmd != SPINEL_CMD_RESET); start += Snprintf(start, static_cast(end - start), ", key:%s", spinel_prop_key_to_cstr(key)); diff --git a/src/lib/spinel/radio_spinel.hpp b/src/lib/spinel/radio_spinel.hpp index 69af0a557..c6eb1fdaa 100644 --- a/src/lib/spinel/radio_spinel.hpp +++ b/src/lib/spinel/radio_spinel.hpp @@ -46,6 +46,103 @@ namespace ot { namespace Spinel { +/** + * Maximum number of Spinel Interface IDs. + * + */ +#ifdef OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +static constexpr uint8_t kSpinelHeaderMaxNumIid = 4; +#else +static constexpr uint8_t kSpinelHeaderMaxNumIid = 1; +#endif + +struct RadioSpinelCallbacks +{ + /** + * This callback notifies user of `RadioSpinel` of a received frame. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aFrame A pointer to the received frame or nullptr if the receive operation failed. + * @param[in] aError kErrorNone when successfully received a frame, + * kErrorAbort when reception was aborted and a frame was not received, + * kErrorNoBufs when a frame could not be received due to lack of rx buffer space. + * + */ + void (*mReceiveDone)(otInstance *aInstance, otRadioFrame *aFrame, Error aError); + + /** + * The callback notifies user of `RadioSpinel` that the transmit operation has completed, providing, if + * applicable, the received ACK frame. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aFrame The transmitted frame. + * @param[in] aAckFrame A pointer to the ACK frame, nullptr if no ACK was received. + * @param[in] aError kErrorNone when the frame was transmitted, + * kErrorNoAck when the frame was transmitted but no ACK was received, + * kErrorChannelAccessFailure tx failed due to activity on the channel, + * kErrorAbort when transmission was aborted for other reasons. + * + */ + void (*mTransmitDone)(otInstance *aInstance, otRadioFrame *aFrame, otRadioFrame *aAckFrame, Error aError); + + /** + * This callback notifies user of `RadioSpinel` that energy scan is complete. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aMaxRssi Maximum RSSI seen on the channel, or `SubMac::kInvalidRssiValue` if failed. + * + */ + void (*mEnergyScanDone)(otInstance *aInstance, int8_t aMaxRssi); + + /** + * This callback notifies user of `RadioSpinel` that the transmission has started. + * + * @param[in] aInstance A pointer to the OpenThread instance structure. + * @param[in] aFrame A pointer to the frame that is being transmitted. + * + */ + void (*mTxStarted)(otInstance *aInstance, otRadioFrame *aFrame); + + /** + * This callback notifies user of `RadioSpinel` that the radio interface switchover has completed. + * + * @param[in] aInstance A pointer to the OpenThread instance structure. + * @param[in] aSuccess A value indicating if the switchover was successful or not. + * + */ + void (*mSwitchoverDone)(otInstance *aInstance, bool aSuccess); + +#if OPENTHREAD_CONFIG_DIAG_ENABLE + /** + * This callback notifies diagnostics module using `RadioSpinel` of a received frame. + * + * This callback is used when diagnostics is enabled. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aFrame A pointer to the received frame or NULL if the receive operation failed. + * @param[in] aError OT_ERROR_NONE when successfully received a frame, + * OT_ERROR_ABORT when reception was aborted and a frame was not received, + * OT_ERROR_NO_BUFS when a frame could not be received due to lack of rx buffer space. + * + */ + void (*mDiagReceiveDone)(otInstance *aInstance, otRadioFrame *aFrame, Error aError); + + /** + * This callback notifies diagnostics module using `RadioSpinel` that the transmission has completed. + * + * This callback is used when diagnostics is enabled. + * + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aFrame A pointer to the frame that was transmitted. + * @param[in] aError OT_ERROR_NONE when the frame was transmitted, + * OT_ERROR_CHANNEL_ACCESS_FAILURE tx could not take place due to activity on the + * channel, OT_ERROR_ABORT when transmission was aborted for other reasons. + * + */ + void (*mDiagTransmitDone)(otInstance *aInstance, otRadioFrame *aFrame, Error aError); +#endif // OPENTHREAD_CONFIG_DIAG_ENABLE +}; + /** * The class for providing a OpenThread radio interface by talking with a radio-only * co-processor(RCP). @@ -72,9 +169,24 @@ public: * @param[in] aSpinelInterface A reference to the Spinel interface. * @param[in] aResetRadio TRUE to reset on init, FALSE to not reset on init. * @param[in] aSkipRcpCompatibilityCheck TRUE to skip RCP compatibility check, FALSE to perform the check. + * @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 aResetRadio, bool aSkipRcpCompatibilityCheck); + void Init(SpinelInterface &aSpinelInterface, + bool aResetRadio, + bool aSkipRcpCompatibilityCheck, + const spinel_iid_t *aIidList, + uint8_t aIidListLength); + + /** + * This method sets the notification callbacks. + * + * @param[in] aCallbacks A pointer to structure with notification callbacks. + * + */ + void SetCallbacks(const struct RadioSpinelCallbacks &aCallbacks); /** * Deinitialize this radio transceiver. @@ -242,7 +354,7 @@ public: * @returns A pointer to the radio version string. * */ - const char *GetVersion(void) const { return mVersion; } + const char *GetVersion(void) const { return sVersion; } /** * Returns the radio capabilities. @@ -250,7 +362,7 @@ public: * @returns The radio capability bit vector. * */ - otRadioCaps GetRadioCaps(void) const { return mRadioCaps; } + otRadioCaps GetRadioCaps(void) const { return sRadioCaps; } /** * Gets the most recent RSSI measurement. @@ -322,6 +434,44 @@ public: otError GetCoexMetrics(otRadioCoexMetrics &aCoexMetrics); #endif // OPENTHREAD_CONFIG_PLATFORM_RADIO_COEX_ENABLE + /** + * Get currently active interface. + * + * @param[out] aIid IID of the interface that owns the radio. + * + * @retval OT_ERROR_NONE Successfully got the property. + * @retval OT_ERROR_RESPONSE_TIMEOUT Failed due to no response received from the transceiver. + * @retval OT_ERROR_NOT_IMPLEMENTED Failed due to lack of the support in radio + * @retval OT_ERROR_INVALID_COMMAND Platform supports all interfaces simultaneously. + * (i.e. no active/inactive interface concept in the platform level) + * + */ + otError GetMultipanActiveInterface(spinel_iid_t *aIid); + + /** + * Sets specified radio interface active + * + * This function allows selecting currently active radio interface on platforms that do not support parallel + * communication on multiple interfaces. I.e. if more than one interface is in receive state calling + * SetMultipanActiveInterface guarantees that specified interface will not be losing frames. This function + * returns if the request was received properly. After interface switching is complete SwitchoverDone callback is + * Invoked. Switching interfaces may take longer if aCompletePending is set true. + * + * @param[in] aIid IID of the interface to set active. + * @param[in] aCompletePending Set true if pending radio operation should complete first(Soft switch) or false if + * ongoing operations should be interrupted (Force switch). + * + * @retval OT_ERROR_NONE Successfully requested interface switch. + * @retval OT_ERROR_BUSY Failed due to another operation on going. + * @retval OT_ERROR_RESPONSE_TIMEOUT Failed due to no response received from the transceiver. + * @retval OT_ERROR_NOT_IMPLEMENTED Failed due to lack of support in radio for the given interface id or + * @retval OT_ERROR_INVALID_COMMAND Platform supports all interfaces simultaneously + * (i.e. no active/inactive interface concept in the platform level) + * @retval OT_ERROR_ALREADY Given interface is already active. + * + */ + otError SetMultipanActiveInterface(spinel_iid_t aIid, bool aCompletePending); + /** * Returns a reference to the transmit buffer. * @@ -1004,6 +1154,17 @@ 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); void HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength); @@ -1052,6 +1213,8 @@ private: SpinelInterface::RxFrameBuffer mRxFrameBuffer; SpinelInterface *mSpinelInterface; + RadioSpinelCallbacks mCallbacks; ///< Callbacks for notifications of higher layer. + uint16_t mCmdTidsInUse; ///< Used transaction ids. spinel_tid_t mCmdNextTid; ///< Next available transaction id. spinel_tid_t mTxRadioTid; ///< The transaction id used to send a radio frame. @@ -1061,6 +1224,8 @@ private: va_list mPropertyArgs; ///< The arguments pack or unpack spinel property of current transaction. uint32_t mExpectedCommand; ///< Expected response command of current transaction. otError mError; ///< The result of current transaction. + spinel_iid_t mIid; ///< The spinel interface id used by this process. + spinel_iid_t mIidList[kSpinelHeaderMaxNumIid]; ///< Array of interface ids to accept the incoming spinel frames. uint8_t mRxPsdu[OT_RADIO_FRAME_MAX_SIZE]; uint8_t mTxPsdu[OT_RADIO_FRAME_MAX_SIZE]; @@ -1070,28 +1235,37 @@ private: otRadioFrame mAckRadioFrame; otRadioFrame *mTransmitFrame; ///< Points to the frame to send - otExtAddress mExtendedAddress; - uint16_t mShortAddress; - uint16_t mPanId; - otRadioCaps mRadioCaps; - uint8_t mChannel; - int8_t mRxSensitivity; - otError mTxError; - char mVersion[kVersionStringSize]; - otExtAddress mIeeeEui64; + otExtAddress mExtendedAddress; + uint16_t mShortAddress; + uint16_t mPanId; + uint8_t mChannel; + int8_t mRxSensitivity; + otError mTxError; + static char sVersion[kVersionStringSize]; + static otExtAddress sIeeeEui64; + static otRadioCaps sRadioCaps; State mState; - bool mIsPromiscuous : 1; ///< Promiscuous mode. - bool mRxOnWhenIdle : 1; ///< RxOnWhenIdle mode. - bool mIsReady : 1; ///< NCP ready. - bool mSupportsLogStream : 1; ///< RCP supports `LOG_STREAM` property with OpenThread log meta-data format. - bool mSupportsResetToBootloader : 1; ///< RCP supports resetting into bootloader mode. - bool mIsTimeSynced : 1; ///< Host has calculated the time difference between host and RCP. + bool mIsPromiscuous : 1; ///< Promiscuous mode. + bool mRxOnWhenIdle : 1; ///< RxOnWhenIdle mode. + bool mIsTimeSynced : 1; ///< Host has calculated the time difference between host and RCP. + + static bool sIsReady; ///< NCP ready. + static bool sSupportsLogStream; ///< RCP supports `LOG_STREAM` property with OpenThread log meta-data format. + static bool sSupportsResetToBootloader; ///< RCP supports resetting into bootloader mode. #if OPENTHREAD_SPINEL_CONFIG_RCP_RESTORATION_MAX_COUNT > 0 + enum + { + kRcpFailureNone, + kRcpFailureTimeout, + kRcpFailureUnexpectedReset, + }; + bool mResetRadioOnStartup : 1; ///< Whether should send reset command when init. int16_t mRcpFailureCount; ///< Count of consecutive RCP failures. + uint8_t mRcpFailure : 2; ///< RCP failure reason, should recover and retry operation. // Properties set by core. uint8_t mKeyIdMode; @@ -1116,7 +1290,6 @@ private: bool mTransmitPowerSet : 1; ///< Whether transmit power has been set. bool mCoexEnabledSet : 1; ///< Whether coex enabled has been set. bool mFemLnaGainSet : 1; ///< Whether FEM LNA gain has been set. - bool mRcpFailed : 1; ///< RCP failure happened, should recover and retry operation. bool mEnergyScanning : 1; ///< If fails while scanning, restarts scanning. bool mMacFrameCounterSet : 1; ///< Whether the MAC frame counter has been set. diff --git a/src/lib/spinel/spinel.c b/src/lib/spinel/spinel.c index 963f27626..82ea55556 100644 --- a/src/lib/spinel/spinel.c +++ b/src/lib/spinel/spinel.c @@ -1548,6 +1548,8 @@ const char *spinel_status_to_cstr(spinel_status_t status) {SPINEL_STATUS_ITEM_NOT_FOUND, "ITEM_NOT_FOUND"}, {SPINEL_STATUS_INVALID_COMMAND_FOR_PROP, "INVALID_COMMAND_FOR_PROP"}, {SPINEL_STATUS_RESPONSE_TIMEOUT, "RESPONSE_TIMEOUT"}, + {SPINEL_STATUS_SWITCHOVER_DONE, "SWITCHOVER_DONE"}, + {SPINEL_STATUS_SWITCHOVER_FAILED, "SWITCHOVER_FAILED"}, {SPINEL_STATUS_JOIN_FAILURE, "JOIN_FAILURE"}, {SPINEL_STATUS_JOIN_SECURITY, "JOIN_SECURITY"}, {SPINEL_STATUS_JOIN_NO_PEERS, "JOIN_NO_PEERS"}, diff --git a/src/lib/spinel/spinel.h b/src/lib/spinel/spinel.h index a4802af9b..5d8094db4 100644 --- a/src/lib/spinel/spinel.h +++ b/src/lib/spinel/spinel.h @@ -78,16 +78,16 @@ * * The Interface Identifier (IID) is a number between 0 and 3, which * is associated by the OS with a specific NCP. This allows the protocol - * to support up to 4 NCPs under same connection. + * to support multiple networks under same connection. * * The least significant bits of the header represent the Transaction * Identifier (TID). The TID is used for correlating responses to the * commands which generated them. * * When a command is sent from the host, any reply to that command sent - * by the NCP will use the same value for the TID. When the host - * receives a frame that matches the TID of the command it sent, it can - * easily recognize that frame as the actual response to that command. + * by the NCP will use the same value for the IID and TID. When the host + * receives a frame that matches the IID and TID of the command it sent, it + * can easily recognize that frame as the actual response to that command. * * The TID value of zero (0) is used for commands to which a correlated * response is not expected or needed, such as for unsolicited update @@ -514,6 +514,9 @@ enum SPINEL_STATUS_UNKNOWN_NEIGHBOR = 22, ///< The neighbor is unknown. SPINEL_STATUS_NOT_CAPABLE = 23, ///< The target is not capable of handling requested operation. SPINEL_STATUS_RESPONSE_TIMEOUT = 24, ///< No response received from remote node + SPINEL_STATUS_SWITCHOVER_DONE = + 25, ///< Radio interface switch completed successfully (SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE) + SPINEL_STATUS_SWITCHOVER_FAILED = 26, ///< Radio interface switch failed (SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE) SPINEL_STATUS_JOIN__BEGIN = 104, @@ -885,6 +888,7 @@ typedef struct typedef int spinel_ssize_t; typedef unsigned int spinel_size_t; +typedef uint8_t spinel_iid_t; typedef uint8_t spinel_tid_t; enum @@ -1451,8 +1455,6 @@ enum * * Provides number of interfaces. * - * Currently always reads as 1. - * */ SPINEL_PROP_INTERFACE_COUNT = 6, @@ -4863,6 +4865,24 @@ enum SPINEL_PROP_RCP_EXT__END = 0x900, + SPINEL_PROP_MULTIPAN__BEGIN = 0x900, + + /// Multipan interface selection. + /** Format: `C` + * Type: Read-Write + * + * `C`: b[0-1] - Interface id. + * b[7] - 1: Complete pending radio operation, 0: immediate(force) switch. + * + * This feature gets or sets the radio interface to be used in multipan configuration + * + * Default value: 0 + * + */ + SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE = SPINEL_PROP_MULTIPAN__BEGIN + 0, + + SPINEL_PROP_MULTIPAN__END = 0x910, + SPINEL_PROP_NEST__BEGIN = 0x3BC0, SPINEL_PROP_NEST_STREAM_MFG = SPINEL_PROP_NEST__BEGIN + 0, @@ -4957,11 +4977,15 @@ typedef uint32_t spinel_prop_key_t; #define SPINEL_HEADER_IID_SHIFT 4 #define SPINEL_HEADER_IID_MASK (3 << SPINEL_HEADER_IID_SHIFT) +#define SPINEL_HEADER_IID(iid) (static_cast((iid) << SPINEL_HEADER_IID_SHIFT)) +#define SPINEL_HEADER_IID_MAX 3 -#define SPINEL_HEADER_IID_0 (0 << SPINEL_HEADER_IID_SHIFT) -#define SPINEL_HEADER_IID_1 (1 << SPINEL_HEADER_IID_SHIFT) -#define SPINEL_HEADER_IID_2 (2 << SPINEL_HEADER_IID_SHIFT) -#define SPINEL_HEADER_IID_3 (3 << SPINEL_HEADER_IID_SHIFT) +#define SPINEL_HEADER_IID_0 SPINEL_HEADER_IID(0) +#define SPINEL_HEADER_IID_1 SPINEL_HEADER_IID(1) +#define SPINEL_HEADER_IID_2 SPINEL_HEADER_IID(2) +#define SPINEL_HEADER_IID_3 SPINEL_HEADER_IID(3) + +#define SPINEL_HEADER_INVALID_IID 0xFF #define SPINEL_HEADER_GET_IID(x) (((x)&SPINEL_HEADER_IID_MASK) >> SPINEL_HEADER_IID_SHIFT) #define SPINEL_HEADER_GET_TID(x) (spinel_tid_t)(((x)&SPINEL_HEADER_TID_MASK) >> SPINEL_HEADER_TID_SHIFT) @@ -4976,6 +5000,10 @@ typedef uint32_t spinel_prop_key_t; #define SPINEL_BEACON_THREAD_FLAG_NATIVE (1 << 3) +#define SPINEL_MULTIPAN_INTERFACE_SOFT_SWITCH_SHIFT 7 +#define SPINEL_MULTIPAN_INTERFACE_SOFT_SWITCH_MASK (1 << SPINEL_MULTIPAN_INTERFACE_SOFT_SWITCH_SHIFT) +#define SPINEL_MULTIPAN_INTERFACE_ID_MASK 0x03 + // ---------------------------------------------------------------------------- enum diff --git a/src/lib/spinel/spinel_interface.hpp b/src/lib/spinel/spinel_interface.hpp index 432b8b7ee..10d0c9637 100644 --- a/src/lib/spinel/spinel_interface.hpp +++ b/src/lib/spinel/spinel_interface.hpp @@ -175,9 +175,27 @@ protected: */ bool IsSpinelResetCommand(const uint8_t *aFrame, uint16_t aLength) { - static constexpr uint8_t kSpinelResetCommand[] = {SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_RESET}; - return (aLength >= sizeof(kSpinelResetCommand)) && - (memcmp(aFrame, kSpinelResetCommand, sizeof(kSpinelResetCommand)) == 0); + const uint8_t kSpinelResetCommandLength = 2; + bool resetCmd = false; + + if (aLength >= kSpinelResetCommandLength) + { +#ifndef OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + // Validate the iid. + VerifyOrExit(((aFrame[0] & SPINEL_HEADER_IID_MASK) == SPINEL_HEADER_IID_0)); +#endif + + // Validate the header flag by masking out the iid bits as it is validated above. + VerifyOrExit(((aFrame[0] & ~SPINEL_HEADER_IID_MASK) == SPINEL_HEADER_FLAG)); + + // Validate the reset command. + VerifyOrExit(aFrame[1] == SPINEL_CMD_RESET); + + ExitNow(resetCmd = true); + } + + exit: + return resetCmd; } }; } // namespace Spinel diff --git a/src/ncp/BUILD.gn b/src/ncp/BUILD.gn index 2adfdecaf..2bb99814a 100644 --- a/src/ncp/BUILD.gn +++ b/src/ncp/BUILD.gn @@ -31,6 +31,7 @@ openthread_ncp_sources = [ "changed_props_set.cpp", "changed_props_set.hpp", "example_vendor_hook.cpp", + "multipan_platform.cpp", "ncp_base.cpp", "ncp_base.hpp", "ncp_base_dispatcher.cpp", diff --git a/src/ncp/CMakeLists.txt b/src/ncp/CMakeLists.txt index 84062470b..25d7e2de1 100644 --- a/src/ncp/CMakeLists.txt +++ b/src/ncp/CMakeLists.txt @@ -33,6 +33,7 @@ set(COMMON_INCLUDES set(COMMON_SOURCES changed_props_set.cpp + multipan_platform.cpp ncp_base.cpp ncp_base_dispatcher.cpp ncp_base_radio.cpp @@ -68,6 +69,15 @@ endif() if(OT_RCP) include(radio.cmake) + if(OT_MULTIPAN_RCP) + target_compile_options(ot-config-radio + INTERFACE + "-DOPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE=1" + "-DOPENTHREAD_CONFIG_LOG_PREPEND_UPTIME=0" # Not supporting multiple instances + "-DOPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE=1" + "-DOPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE=1" + ) + endif() endif() set_property(SOURCE ncp_base_mtd.cpp diff --git a/src/ncp/example_vendor_hook.cpp b/src/ncp/example_vendor_hook.cpp index 62c0b6aaa..116b8493c 100644 --- a/src/ncp/example_vendor_hook.cpp +++ b/src/ncp/example_vendor_hook.cpp @@ -141,6 +141,12 @@ public: : ot::Ncp::NcpHdlc(aInstance, &NcpVendorUart::SendHdlc) { } +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO + NcpVendorUart(ot::Instance **aInstances, uint8_t count) + : ot::Ncp::NcpHdlc(aInstances, count, &NcpVendorUart::SendHdlc) + { + } +#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO // Add public/private methods or member variables }; @@ -159,5 +165,28 @@ extern "C" void otAppNcpInit(otInstance *aInstance) // assert(false); } } +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO +extern "C" void otAppNcpInitMulti(otInstance **aInstances, uint8_t count) +{ + NcpVendorUart *ncpVendor = nullptr; + ot::Instance *instances[SPINEL_HEADER_IID_MAX]; + + OT_ASSERT(count < SPINEL_HEADER_IID_MAX); + OT_ASSERT(count > 0); + OT_ASSERT(aInstances[0] != nullptr); + + for (int i = 0; i < count; i++) + { + instances[i] = static_cast(aInstances[i]); + } + + ncpVendor = new (&sNcpVendorRaw) NcpVendorUart(instances, count); + + if (ncpVendor == nullptr || ncpVendor != ot::Ncp::NcpBase::GetNcpInstance()) + { + OT_ASSERT(false); + } +} +#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO #endif // #if OPENTHREAD_ENABLE_NCP_VENDOR_HOOK diff --git a/src/ncp/multipan_platform.cpp b/src/ncp/multipan_platform.cpp new file mode 100644 index 000000000..92374e322 --- /dev/null +++ b/src/ncp/multipan_platform.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2023, 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 the multipan radio platform callbacks into OpenThread and default/weak radio platform APIs. + */ + +#include +#include + +#include "common/as_core_type.hpp" +#include "common/code_utils.hpp" +#include "instance/instance.hpp" +#include "ncp/ncp_base.hpp" + +using namespace ot; + +//--------------------------------------------------------------------------------------------------------------------- +// otPlatRadio callbacks + +otInstance *otPlatMultipanIidToInstance(uint8_t aIid) +{ + Ncp::NcpBase *ncpBase = Ncp::NcpBase::GetNcpInstance(); + OT_ASSERT(ncpBase); + + return ncpBase->IidToInstance(aIid); +} + +uint8_t otPlatMultipanInstanceToIid(otInstance *aInstance) +{ + Ncp::NcpBase *ncpBase = Ncp::NcpBase::GetNcpInstance(); + OT_ASSERT(ncpBase); + + return ncpBase->InstanceToIid(static_cast(aInstance)); +} + +#if OPENTHREAD_RADIO && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + +void otPlatMultipanSwitchoverDone(otInstance *aInstance, bool success) +{ + Ncp::NcpBase *ncpBase = Ncp::NcpBase::GetNcpInstance(); + OT_ASSERT(ncpBase); + + ncpBase->NotifySwitchoverDone(aInstance, success); + + return; +} + +#else // OPENTHREAD_RADIO && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + +// default implementation +OT_TOOL_WEAK void otPlatMultipanSwitchoverDone(otInstance *aInstance, bool success) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(success); +} + +#endif // OPENTHREAD_RADIO && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + +//--------------------------------------------------------------------------------------------------------------------- +// Default/weak implementation of multipan APIs + +OT_TOOL_WEAK otError otPlatMultipanGetActiveInstance(otInstance **aInstance) +{ + OT_UNUSED_VARIABLE(aInstance); + + return kErrorNotImplemented; +} + +OT_TOOL_WEAK otError otPlatMultipanSetActiveInstance(otInstance *aInstance, bool aCompletePending) +{ + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aCompletePending); + + return kErrorNotImplemented; +} diff --git a/src/ncp/ncp_base.cpp b/src/ncp/ncp_base.cpp index 288373fc7..e0b4c1881 100644 --- a/src/ncp/ncp_base.cpp +++ b/src/ncp/ncp_base.cpp @@ -55,6 +55,43 @@ 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); } @@ -202,6 +239,29 @@ static spinel_status_t ResetReasonToSpinelStatus(otPlatResetReason aReason) 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)) @@ -222,12 +282,10 @@ NcpBase::NcpBase(Instance *aInstance) , mAllowPeekDelegate(nullptr) , mAllowPokeDelegate(nullptr) #endif - , mNextExpectedTid(0) , mResponseQueueHead(0) , mResponseQueueTail(0) , mAllowLocalNetworkDataChange(false) , mRequireJoinExistingNetwork(false) - , mIsRawStreamEnabled(false) , mPcapEnabled(false) , mDisableStreamWrite(false) , mShouldEmitChildTableUpdate(false) @@ -237,11 +295,7 @@ NcpBase::NcpBase(Instance *aInstance) #if OPENTHREAD_FTD , mPreferredRouteId(0) #endif -#if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE - , mCurTransmitTID(0) - , mCurScanChannel(kInvalidScanChannel) - , mSrcMatchEnabled(false) -#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE + , mCurCommandIid(0) #if OPENTHREAD_MTD || OPENTHREAD_FTD , mInboundSecureIpFrameCounter(0) , mInboundInsecureIpFrameCounter(0) @@ -267,6 +321,13 @@ NcpBase::NcpBase(Instance *aInstance) 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); @@ -303,6 +364,8 @@ NcpBase::NcpBase(Instance *aInstance) NcpBase *NcpBase::GetNcpInstance(void) { return sNcpInstance; } +spinel_iid_t NcpBase::GetCurCommandIid(void) const { return mCurCommandIid; } + void NcpBase::ResetCounters(void) { mFramingErrorCounter = 0; @@ -348,8 +411,20 @@ void NcpBase::HandleReceive(const uint8_t *aBuf, uint16_t aBufLength) mRxSpinelFrameCounter++; - // We only support IID zero for now. - if (SPINEL_HEADER_GET_IID(header) != 0) + 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(); @@ -378,12 +453,12 @@ void NcpBase::HandleReceive(const uint8_t *aBuf, uint16_t aBufLength) tid = SPINEL_HEADER_GET_TID(header); - if ((mNextExpectedTid != 0) && (tid != mNextExpectedTid)) + if ((mNextExpectedTid[mCurCommandIid] != 0) && (tid != mNextExpectedTid[mCurCommandIid])) { mRxSpinelOutOfOrderTidCounter++; } - mNextExpectedTid = SPINEL_GET_NEXT_TID(tid); + mNextExpectedTid[mCurCommandIid] = SPINEL_GET_NEXT_TID(tid); exit: mDisableStreamWrite = false; @@ -469,7 +544,7 @@ 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_IID_0; + uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID; spinel_prop_key_t streamPropKey; if (aStreamId == 0) @@ -644,7 +719,7 @@ unsigned int NcpBase::ConvertLogRegion(otLogRegion aLogRegion) void NcpBase::Log(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aLogString) { otError error = OT_ERROR_NONE; - uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0; + 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)); @@ -699,6 +774,7 @@ uint8_t NcpBase::GetWrappedResponseQueueIndex(uint8_t 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; @@ -731,13 +807,13 @@ otError NcpBase::EnqueueResponse(uint8_t aHeader, ResponseType aType, unsigned i // 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) + if (tid != mNextExpectedTid[iid]) { for (uint8_t cur = mResponseQueueHead; cur < mResponseQueueTail; cur++) { entry = &mResponseQueue[GetWrappedResponseQueueIndex(cur)]; - if (entry->mIsInUse && (entry->mTid == tid)) + if (entry->mIsInUse && (entry->mIid == iid) && (entry->mTid == tid)) { // Entry is just marked here and will be removed // from `SendQueuedResponses()`. @@ -752,6 +828,7 @@ otError NcpBase::EnqueueResponse(uint8_t aHeader, ResponseType aType, unsigned i entry = &mResponseQueue[GetWrappedResponseQueueIndex(mResponseQueueTail)]; + entry->mIid = iid; entry->mTid = tid; entry->mIsInUse = true; entry->mType = aType; @@ -773,8 +850,8 @@ otError NcpBase::SendQueuedResponses(void) if (entry.mIsInUse) { - uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0; - + uint8_t header = SPINEL_HEADER_FLAG; + header |= SPINEL_HEADER_IID(entry.mIid); header |= static_cast(entry.mTid << SPINEL_HEADER_TID_SHIFT); if (entry.mType == kResponseTypeLastStatus) @@ -857,11 +934,11 @@ void NcpBase::UpdateChangedProps(void) status = ResetReasonToSpinelStatus(otPlatGetResetReason(mInstance)); } - SuccessOrExit(WriteLastStatusFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, status)); + SuccessOrExit(WriteLastStatusFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID, status)); } else if (mDidInitialUpdates) { - SuccessOrExit(WritePropertyValueIsFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, propKey)); + SuccessOrExit(WritePropertyValueIsFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID, propKey)); } mChangedPropsSet.RemoveEntry(index); @@ -1102,7 +1179,7 @@ otError NcpBase::WriteLastStatusFrame(uint8_t aHeader, spinel_status_t aLastStat { otError error = OT_ERROR_NONE; - if (SPINEL_HEADER_GET_IID(aHeader) == 0) + if (SPINEL_HEADER_GET_IID(aHeader) == SPINEL_HEADER_GET_IID(SPINEL_HEADER_TX_NOTIFICATION_IID)) { mLastStatus = aLastStatus; } @@ -1201,15 +1278,15 @@ otError NcpBase::CommandHandler_RESET(uint8_t aHeader) { otInstanceResetRadioStack(mInstance); - mIsRawStreamEnabled = false; - mCurTransmitTID = 0; - mCurScanChannel = kInvalidScanChannel; - mSrcMatchEnabled = false; + mIsRawStreamEnabled[mCurCommandIid] = false; + mCurTransmitTID[mCurCommandIid] = 0; + mCurScanChannel[mCurCommandIid] = kInvalidScanChannel; + mSrcMatchEnabled[mCurCommandIid] = false; ResetCounters(); - SuccessOrAssert( - error = WriteLastStatusFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_STATUS_RESET_POWER_ON)); + 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) @@ -1401,7 +1478,7 @@ template <> otError NcpBase::HandlePropertySet(void) // Make sure we are update the receiving channel if raw link is enabled and we have raw // stream enabled already - if (otLinkRawIsEnabled(mInstance) && mIsRawStreamEnabled) + if (otLinkRawIsEnabled(mInstance) && mIsRawStreamEnabled[mCurCommandIid]) { error = otLinkRawReceive(mInstance); } @@ -1515,7 +1592,7 @@ template <> otError NcpBase::HandlePropertyGet(void) template <> otError NcpBase::HandlePropertyGet(void) { - return mEncoder.WriteBool(mIsRawStreamEnabled); + return mEncoder.WriteBool(mIsRawStreamEnabled[mCurCommandIid]); } template <> otError NcpBase::HandlePropertySet(void) @@ -1541,7 +1618,7 @@ template <> otError NcpBase::HandlePropertySet otError NcpBase::HandlePropertyGet(void) if (otLinkRawIsEnabled(mInstance)) { - scanState = (mCurScanChannel == kInvalidScanChannel) ? SPINEL_SCAN_STATE_IDLE : SPINEL_SCAN_STATE_ENERGY; + scanState = (mCurScanChannel[mCurCommandIid] == kInvalidScanChannel) ? SPINEL_SCAN_STATE_IDLE + : SPINEL_SCAN_STATE_ENERGY; } else @@ -1688,11 +1766,11 @@ template <> otError NcpBase::HandlePropertySet(void) // Make sure we aren't already scanning and that we have // only 1 bit set for the channel mask. - VerifyOrExit(mCurScanChannel == kInvalidScanChannel, error = OT_ERROR_INVALID_STATE); + VerifyOrExit(mCurScanChannel[mCurCommandIid] == kInvalidScanChannel, error = OT_ERROR_INVALID_STATE); VerifyOrExit(HasOnly1BitSet(mScanChannelMask), error = OT_ERROR_INVALID_ARGS); - scanChannel = IndexOfMSB(mScanChannelMask); - mCurScanChannel = static_cast(scanChannel); + scanChannel = IndexOfMSB(mScanChannelMask); + mCurScanChannel[mCurCommandIid] = static_cast(scanChannel); error = otLinkRawEnergyScan(mInstance, scanChannel, mScanPeriod, LinkRawEnergyScanDone); } @@ -1768,8 +1846,8 @@ exit: if (error != OT_ERROR_NONE) { - IgnoreError( - WritePropertyValueIsFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_PROP_UNSOL_UPDATE_FILTER)); + IgnoreError(WritePropertyValueIsFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID, + SPINEL_PROP_UNSOL_UPDATE_FILTER)); } return error; @@ -1996,7 +2074,19 @@ template <> otError NcpBase::HandlePropertyGet(void) template <> otError NcpBase::HandlePropertyGet(void) { - return mEncoder.WriteUint8(1); // Only one interface for now + 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 diff --git a/src/ncp/ncp_base.hpp b/src/ncp/ncp_base.hpp index b618101c3..7b8d94f87 100644 --- a/src/ncp/ncp_base.hpp +++ b/src/ncp/ncp_base.hpp @@ -62,6 +62,20 @@ #include "lib/spinel/spinel_decoder.hpp" #include "lib/spinel/spinel_encoder.hpp" +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +#define SPINEL_HEADER_IID_BROADCAST OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID +#else +#define SPINEL_HEADER_IID_BROADCAST SPINEL_HEADER_IID_0 +#endif + +// In case of host<->ncp<->rcp configuration, notifications shall be +// received on broadcast iid on ncp, but transmitted on IID 0 to host. +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO +#define SPINEL_HEADER_TX_NOTIFICATION_IID SPINEL_HEADER_IID_BROADCAST +#else +#define SPINEL_HEADER_TX_NOTIFICATION_IID SPINEL_HEADER_IID_0 +#endif + namespace ot { namespace Ncp { @@ -72,6 +86,11 @@ public: { kSpinelCmdHeaderSize = 2, ///< Size of spinel command header (in bytes). kSpinelPropIdSize = 3, ///< Size of spinel property identifier (in bytes). +#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_RADIO + kSpinelInterfaceCount = SPINEL_HEADER_IID_MAX + 1, // Number of supported spinel interfaces +#else + kSpinelInterfaceCount = 1, // Only one interface supported in single instance configuration +#endif }; /** @@ -82,6 +101,18 @@ public: */ explicit NcpBase(Instance *aInstance); +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO + /** + * Creates and initializes an NcpBase instance. + * + * @param[in] aInstances The OpenThread instances structure pointer array. + * @param[in] aCount Number of the instances in the array. + * + */ + explicit NcpBase(Instance **aInstances, uint8_t aCount); + +#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO + /** * Returns the pointer to the single NCP instance. * @@ -90,6 +121,50 @@ public: */ static NcpBase *GetNcpInstance(void); + /** + * Returns an IID for the given instance + * + * Returned IID is an integer value that must be shifted by SPINEL_HEADER_IID_SHIFT before putting into spinel + * header. If multipan interface is not enabled or build is not for RCP IID=0 is returned. If nullptr is passed it + * matches broadcast IID in current implementation. Broadcast IID is also returned in case no match was found. + * + * @param[in] aInstance Instance pointer to match with IID + * + * @returns Spinel Interface Identifier to use for communication for this instance + * + */ + uint8_t InstanceToIid(Instance *aInstance); + + /** + * Returns an OT instance for the given IID + * + * Returns an OpenThread instance object associated to the given IID. + * If multipan interface is not enabled or build is not for RCP returned value is the same instance object + * regardless of the aIid parameter In current implementation nullptr is returned for broadcast IID and values + * exceeding the instances count but lower than kSpinelInterfaceCount. + * + * @param[in] aIid IID used in the Spinel communication + * + * @returns OpenThread instance object associated with the given IID + * + */ + Instance *IidToInstance(uint8_t aIid); + +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + /** + * Called to send notification to host about switchower results. + */ + void NotifySwitchoverDone(otInstance *aInstance, bool aSuccess); +#endif + + /** + * This method returns the IID of the current spinel command. + * + * @returns IID. + * + */ + spinel_iid_t GetCurCommandIid(void) const; + /** * Sends data to host via specific stream. * @@ -172,6 +247,7 @@ protected: */ struct ResponseEntry { + uint8_t mIid : 2; ///< Spinel interface id. uint8_t mTid : 4; ///< Spinel transaction id. bool mIsInUse : 1; ///< `true` if this entry is in use, `false` otherwise. ResponseType mType : 2; ///< Response type. @@ -246,17 +322,26 @@ protected: #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE otError PackRadioFrame(otRadioFrame *aFrame, otError aError); +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + void NotifySwitchoverDone(bool aSuccess); +#endif + static void LinkRawReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError); - void LinkRawReceiveDone(otRadioFrame *aFrame, otError aError); + void LinkRawReceiveDone(uint8_t aIid, otRadioFrame *aFrame, otError aError); static void LinkRawTransmitDone(otInstance *aInstance, otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError); - void LinkRawTransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError); + void LinkRawTransmitDone(uint8_t aIid, otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError); static void LinkRawEnergyScanDone(otInstance *aInstance, int8_t aEnergyScanMaxRssi); - void LinkRawEnergyScanDone(int8_t aEnergyScanMaxRssi); + void LinkRawEnergyScanDone(uint8_t aIid, int8_t aEnergyScanMaxRssi); + + static inline uint8_t GetNcpBaseIid(otInstance *aInstance) + { + return sNcpInstance->InstanceToIid(static_cast(aInstance)); + } #endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE @@ -534,11 +619,6 @@ protected: static NcpBase *sNcpInstance; static spinel_status_t ThreadErrorToSpinelStatus(otError aError); static uint8_t LinkFlagsToFlagByte(bool aRxOnWhenIdle, bool aDeviceType, bool aNetworkData); - Instance *mInstance; - Spinel::Buffer mTxFrameBuffer; - Spinel::Encoder mEncoder; - Spinel::Decoder mDecoder; - bool mHostPowerStateInProgress; enum { @@ -547,6 +627,15 @@ protected: kInvalidScanChannel = -1, // Invalid scan channel. }; + Instance *mInstance; +#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_RADIO + Instance *mInstances[kSpinelInterfaceCount]; +#endif + Spinel::Buffer mTxFrameBuffer; + Spinel::Encoder mEncoder; + Spinel::Decoder mDecoder; + bool mHostPowerStateInProgress; + spinel_status_t mLastStatus; uint32_t mScanChannelMask; uint16_t mScanPeriod; @@ -569,7 +658,7 @@ protected: uint8_t mTxBuffer[kTxBufferSize]; - spinel_tid_t mNextExpectedTid; + spinel_tid_t mNextExpectedTid[kSpinelInterfaceCount]; uint8_t mResponseQueueHead; uint8_t mResponseQueueTail; @@ -577,7 +666,7 @@ protected: bool mAllowLocalNetworkDataChange; bool mRequireJoinExistingNetwork; - bool mIsRawStreamEnabled; + bool mIsRawStreamEnabled[kSpinelInterfaceCount]; bool mPcapEnabled; bool mDisableStreamWrite; bool mShouldEmitChildTableUpdate; @@ -591,11 +680,12 @@ protected: #endif uint8_t mPreferredRouteId; #endif + uint8_t mCurCommandIid; #if OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE - uint8_t mCurTransmitTID; - int8_t mCurScanChannel; - bool mSrcMatchEnabled; + uint8_t mCurTransmitTID[kSpinelInterfaceCount]; + int8_t mCurScanChannel[kSpinelInterfaceCount]; + bool mSrcMatchEnabled[kSpinelInterfaceCount]; #endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE #if OPENTHREAD_MTD || OPENTHREAD_FTD diff --git a/src/ncp/ncp_base_dispatcher.cpp b/src/ncp/ncp_base_dispatcher.cpp index 568b95964..e653f8311 100644 --- a/src/ncp/ncp_base_dispatcher.cpp +++ b/src/ncp/ncp_base_dispatcher.cpp @@ -217,6 +217,9 @@ NcpBase::PropertyHandler NcpBase::FindGetPropertyHandler(spinel_prop_key_t aKey) #if OPENTHREAD_CONFIG_MAC_CSL_TRANSMITTER_ENABLE OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_RCP_CSL_UNCERTAINTY), #endif +#if OPENTHREAD_RADIO && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE), +#endif #if OPENTHREAD_MTD || OPENTHREAD_FTD OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_UNSOL_UPDATE_FILTER), OT_NCP_GET_HANDLER_ENTRY(SPINEL_PROP_UNSOL_UPDATE_LIST), @@ -489,6 +492,9 @@ NcpBase::PropertyHandler NcpBase::FindSetPropertyHandler(spinel_prop_key_t aKey) OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_RCP_ENH_ACK_PROBING), #endif #endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE +#if OPENTHREAD_RADIO && OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE), +#endif #if OPENTHREAD_MTD || OPENTHREAD_FTD OT_NCP_SET_HANDLER_ENTRY(SPINEL_PROP_UNSOL_UPDATE_FILTER), #if OPENTHREAD_CONFIG_JAM_DETECTION_ENABLE diff --git a/src/ncp/ncp_base_radio.cpp b/src/ncp/ncp_base_radio.cpp index 428037e4b..cad3ef52d 100644 --- a/src/ncp/ncp_base_radio.cpp +++ b/src/ncp/ncp_base_radio.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -123,15 +124,32 @@ exit: return error; } -void NcpBase::LinkRawReceiveDone(otInstance *, otRadioFrame *aFrame, otError aError) +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +void NcpBase::NotifySwitchoverDone(otInstance *aInstance, bool aSuccess) { - sNcpInstance->LinkRawReceiveDone(aFrame, aError); + OT_UNUSED_VARIABLE(aInstance); + NotifySwitchoverDone(aSuccess); } -void NcpBase::LinkRawReceiveDone(otRadioFrame *aFrame, otError aError) +void NcpBase::NotifySwitchoverDone(bool aSuccess) { - uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0; + uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_TX_NOTIFICATION_IID; + spinel_status_t result = aSuccess ? SPINEL_STATUS_SWITCHOVER_DONE : SPINEL_STATUS_SWITCHOVER_FAILED; + IgnoreError(WriteLastStatusFrame(header, result)); +} +#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + +void NcpBase::LinkRawReceiveDone(otInstance *aInstance, otRadioFrame *aFrame, otError aError) +{ + sNcpInstance->LinkRawReceiveDone(GetNcpBaseIid(aInstance), aFrame, aError); +} + +void NcpBase::LinkRawReceiveDone(uint8_t aIid, otRadioFrame *aFrame, otError aError) +{ + uint8_t header = SPINEL_HEADER_FLAG; + + header |= SPINEL_HEADER_IID(aIid); // Append frame header SuccessOrExit(mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_STREAM_RAW)); @@ -142,23 +160,24 @@ exit: return; } -void NcpBase::LinkRawTransmitDone(otInstance *, otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError) +void NcpBase::LinkRawTransmitDone(otInstance *aInstance, otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError) { - sNcpInstance->LinkRawTransmitDone(aFrame, aAckFrame, aError); + sNcpInstance->LinkRawTransmitDone(GetNcpBaseIid(aInstance), aFrame, aAckFrame, aError); } -void NcpBase::LinkRawTransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError) +void NcpBase::LinkRawTransmitDone(uint8_t aIid, otRadioFrame *aFrame, otRadioFrame *aAckFrame, otError aError) { OT_UNUSED_VARIABLE(aFrame); + OT_ASSERT(aIid < kSpinelInterfaceCount); - if (mCurTransmitTID) + if (mCurTransmitTID[aIid]) { - uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0 | mCurTransmitTID; + uint8_t header = SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(aIid) | mCurTransmitTID[aIid]; bool framePending = (aAckFrame != nullptr && static_cast(aAckFrame)->GetFramePending()); bool headerUpdated = static_cast(aFrame)->IsHeaderUpdated(); // Clear cached transmit TID - mCurTransmitTID = 0; + mCurTransmitTID[aIid] = 0; SuccessOrExit(mEncoder.BeginFrame(header, SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_LAST_STATUS)); SuccessOrExit(mEncoder.WriteUintPacked(ThreadErrorToSpinelStatus(aError))); @@ -190,23 +209,24 @@ exit: return; } -void NcpBase::LinkRawEnergyScanDone(otInstance *, int8_t aEnergyScanMaxRssi) +void NcpBase::LinkRawEnergyScanDone(otInstance *aInstance, int8_t aEnergyScanMaxRssi) { - sNcpInstance->LinkRawEnergyScanDone(aEnergyScanMaxRssi); + sNcpInstance->LinkRawEnergyScanDone(GetNcpBaseIid(aInstance), aEnergyScanMaxRssi); } -void NcpBase::LinkRawEnergyScanDone(int8_t aEnergyScanMaxRssi) +void NcpBase::LinkRawEnergyScanDone(uint8_t aIid, int8_t aEnergyScanMaxRssi) { - int8_t scanChannel = mCurScanChannel; + OT_ASSERT(aIid < kSpinelInterfaceCount); + int8_t scanChannel = mCurScanChannel[aIid]; // Clear current scan channel - mCurScanChannel = kInvalidScanChannel; + mCurScanChannel[aIid] = kInvalidScanChannel; // Make sure we are back listening on the original receive channel, // since the energy scan could have been on a different channel. - IgnoreError(otLinkRawReceive(mInstance)); + IgnoreError(otLinkRawReceive(IidToInstance(aIid))); - SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS, + SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(aIid), SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_MAC_ENERGY_SCAN_RESULT)); SuccessOrExit(mEncoder.WriteUint8(static_cast(scanChannel))); @@ -215,7 +235,7 @@ void NcpBase::LinkRawEnergyScanDone(int8_t aEnergyScanMaxRssi) // We are finished with the scan, so send out // a property update indicating such. - SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID_0, SPINEL_CMD_PROP_VALUE_IS, + SuccessOrExit(mEncoder.BeginFrame(SPINEL_HEADER_FLAG | SPINEL_HEADER_IID(aIid), SPINEL_CMD_PROP_VALUE_IS, SPINEL_PROP_MAC_SCAN_STATE)); SuccessOrExit(mEncoder.WriteUint8(SPINEL_SCAN_STATE_IDLE)); @@ -233,7 +253,7 @@ template <> otError NcpBase::HandlePropertyGet(void) template <> otError NcpBase::HandlePropertyGet(void) { // TODO: Would be good to add an `otLinkRaw` API to give the status of source match. - return mEncoder.WriteBool(mSrcMatchEnabled); + return mEncoder.WriteBool(mSrcMatchEnabled[mCurCommandIid]); } template <> otError NcpBase::HandlePropertyGet(void) @@ -250,9 +270,9 @@ template <> otError NcpBase::HandlePropertySet otError NcpBase::HandlePropertySet(void) +{ + uint8_t interface; + Instance *instance; + bool softSwitch; + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = mDecoder.ReadUint8(interface)); + softSwitch = (interface & SPINEL_MULTIPAN_INTERFACE_SOFT_SWITCH_MASK) != 0; + instance = IidToInstance(interface & SPINEL_MULTIPAN_INTERFACE_ID_MASK); + VerifyOrExit(instance != nullptr, error = OT_ERROR_NOT_IMPLEMENTED); // Instance out of range + SuccessOrExit(error = otPlatMultipanSetActiveInstance(instance, softSwitch)); + +exit: + return error; +} +#endif /* OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE */ + otError NcpBase::DecodeStreamRawTxRequest(otRadioFrame &aFrame) { otError error; @@ -449,8 +488,11 @@ exit: otError NcpBase::HandlePropertySet_SPINEL_PROP_STREAM_RAW(uint8_t aHeader) { otError error = OT_ERROR_NONE; + uint8_t iid = SPINEL_HEADER_GET_IID(aHeader); otRadioFrame *frame; + OT_ASSERT(iid < kSpinelInterfaceCount); + VerifyOrExit(otLinkRawIsEnabled(mInstance), error = OT_ERROR_INVALID_STATE); frame = otLinkRawGetTransmitBuffer(mInstance); @@ -463,7 +505,7 @@ otError NcpBase::HandlePropertySet_SPINEL_PROP_STREAM_RAW(uint8_t aHeader) SuccessOrExit(error = otLinkRawTransmit(mInstance, &NcpBase::LinkRawTransmitDone)); // Cache the transaction ID for async response - mCurTransmitTID = SPINEL_HEADER_GET_TID(aHeader); + mCurTransmitTID[iid] = SPINEL_HEADER_GET_TID(aHeader); exit: @@ -537,6 +579,22 @@ exit: return error; } +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +template <> otError NcpBase::HandlePropertyGet(void) +{ + otInstance *instance; + spinel_iid_t iid; + otError error = OT_ERROR_NONE; + + SuccessOrExit(error = otPlatMultipanGetActiveInstance(&instance)); + iid = InstanceToIid(static_cast(instance)); + SuccessOrExit(error = mEncoder.WriteUint8(static_cast(iid))); + +exit: + return error; +} +#endif /* OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE */ + #if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE template <> otError NcpBase::HandlePropertySet(void) { diff --git a/src/ncp/ncp_hdlc.cpp b/src/ncp/ncp_hdlc.cpp index 59da42496..9950de8a3 100644 --- a/src/ncp/ncp_hdlc.cpp +++ b/src/ncp/ncp_hdlc.cpp @@ -77,6 +77,31 @@ extern "C" void otNcpHdlcInit(otInstance *aInstance, otNcpHdlcSendCallback aSend } } +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO + +extern "C" void otNcpHdlcInitMulti(otInstance **aInstances, uint8_t aCount, otNcpHdlcSendCallback aSendCallback) +{ + NcpHdlc *ncpHdlc = nullptr; + ot::Instance *instances[SPINEL_HEADER_IID_MAX]; + + OT_ASSERT(aCount < SPINEL_HEADER_IID_MAX + 1); + OT_ASSERT(aCount > 0); + OT_ASSERT(aInstances[0] != nullptr); + + for (int i = 0; i < aCount; i++) + { + instances[i] = static_cast(aInstances[i]); + } + + ncpHdlc = new (&sNcpRaw) NcpHdlc(instances, aCount, aSendCallback); + + if (ncpHdlc == nullptr || ncpHdlc != NcpBase::GetNcpInstance()) + { + OT_ASSERT(false); + } +} +#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO + #endif // OPENTHREAD_ENABLE_NCP_VENDOR_HOOK == 0 NcpHdlc::NcpHdlc(Instance *aInstance, otNcpHdlcSendCallback aSendCallback) @@ -95,6 +120,26 @@ NcpHdlc::NcpHdlc(Instance *aInstance, otNcpHdlcSendCallback aSendCallback) mTxFrameBuffer.SetFrameAddedCallback(HandleFrameAddedToNcpBuffer, this); } +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO + +NcpHdlc::NcpHdlc(Instance **aInstances, uint8_t aCount, otNcpHdlcSendCallback aSendCallback) + : NcpBase(aInstances, aCount) + , mSendCallback(aSendCallback) + , mFrameEncoder(mHdlcBuffer) + , mState(kStartingFrame) + , mByte(0) + , mHdlcSendImmediate(false) + , mHdlcSendTask(*aInstances[0], EncodeAndSend) +#if OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER + , mTxFrameBufferEncrypterReader(mTxFrameBuffer) +#endif // OPENTHREAD_ENABLE_NCP_SPINEL_ENCRYPTER +{ + mFrameDecoder.Init(mRxBuffer, &NcpHdlc::HandleFrame, this); + mTxFrameBuffer.SetFrameAddedCallback(HandleFrameAddedToNcpBuffer, this); +} + +#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO + void NcpHdlc::HandleFrameAddedToNcpBuffer(void *aContext, Spinel::Buffer::FrameTag aTag, Spinel::Buffer::Priority aPriority, diff --git a/src/ncp/ncp_hdlc.hpp b/src/ncp/ncp_hdlc.hpp index d99ef96de..751611db2 100644 --- a/src/ncp/ncp_hdlc.hpp +++ b/src/ncp/ncp_hdlc.hpp @@ -59,6 +59,19 @@ public: */ explicit NcpHdlc(Instance *aInstance, otNcpHdlcSendCallback aSendCallback); +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO + /** + * Constructor + * + * @param[in] aInstancs The OpenThread instance pointers array. + * @param[in] aCount Number of instances in the array. + * @param[in] aSendCallback Callback for sending data. + * + */ + explicit NcpHdlc(Instance **aInstances, uint8_t aCount, otNcpHdlcSendCallback aSendCallback); + +#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && OPENTHREAD_RADIO + /** * Is called when uart tx is finished. It prepares and sends the next data chunk (if any) to uart. * diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index dfc62408e..3934d0ccb 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -45,6 +45,13 @@ if(OT_DAEMON) set(OT_PLATFORM_DEFINES ${OT_PLATFORM_DEFINES} PARENT_SCOPE) endif() +if(OT_MULTIPAN_RCP) + target_compile_definitions(ot-posix-config + INTERFACE "OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE=1" + ) +endif() + + option(OT_POSIX_INSTALL_EXTERNAL_ROUTES "Install External Routes as IPv6 routes" ON) if(OT_POSIX_INSTALL_EXTERNAL_ROUTES) target_compile_definitions(ot-posix-config diff --git a/src/posix/platform/radio.cpp b/src/posix/platform/radio.cpp index 86901dd7d..491c6db1f 100644 --- a/src/posix/platform/radio.cpp +++ b/src/posix/platform/radio.cpp @@ -66,12 +66,26 @@ Radio::Radio(void) void Radio::Init(const char *aUrl) { - bool resetRadio; - bool skipCompatibilityCheck; + bool resetRadio; + bool skipCompatibilityCheck; + spinel_iid_t iidList[Spinel::kSpinelHeaderMaxNumIid]; + struct ot::Spinel::RadioSpinelCallbacks callbacks; mRadioUrl = aUrl; VerifyOrDie(mRadioUrl.GetPath() != nullptr, OT_EXIT_INVALID_ARGUMENTS); + memset(&callbacks, 0, sizeof(callbacks)); +#if OPENTHREAD_CONFIG_DIAG_ENABLE + callbacks.mDiagReceiveDone = otPlatDiagRadioReceiveDone; + callbacks.mDiagTransmitDone = otPlatDiagRadioTransmitDone; +#endif // OPENTHREAD_CONFIG_DIAG_ENABLE + callbacks.mEnergyScanDone = otPlatRadioEnergyScanDone; + callbacks.mReceiveDone = otPlatRadioReceiveDone; + callbacks.mTransmitDone = otPlatRadioTxDone; + callbacks.mTxStarted = otPlatRadioTxStarted; + + GetIidListFromRadioUrl(iidList); + #if OPENTHREAD_POSIX_VIRTUAL_TIME VirtualTimeInit(); #endif @@ -81,7 +95,10 @@ void Radio::Init(const char *aUrl) resetRadio = !mRadioUrl.HasParam("no-reset"); skipCompatibilityCheck = mRadioUrl.HasParam("skip-rcp-compatibility-check"); - mRadioSpinel.Init(*mSpinelInterface, resetRadio, skipCompatibilityCheck); + + mRadioSpinel.SetCallbacks(callbacks); + mRadioSpinel.Init(*mSpinelInterface, resetRadio, skipCompatibilityCheck, iidList, OT_ARRAY_LENGTH(iidList)); + otLogDebgPlat("instance init:%p - iid = %d", (void *)&mRadioSpinel, iidList[0]); ProcessRadioUrl(mRadioUrl); } @@ -229,6 +246,53 @@ exit: #endif // OPENTHREAD_POSIX_CONFIG_MAX_POWER_TABLE_ENABLE } +void Radio::GetIidListFromRadioUrl(spinel_iid_t (&aIidList)[Spinel::kSpinelHeaderMaxNumIid]) +{ + const char *iidString; + const char *iidListString; + + memset(aIidList, SPINEL_HEADER_INVALID_IID, sizeof(aIidList)); + + iidString = (mRadioUrl.GetValue("iid")); + iidListString = (mRadioUrl.GetValue("iid-list")); + +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + // First entry to the aIidList must be the IID of the host application. + VerifyOrDie(iidString != nullptr, OT_EXIT_INVALID_ARGUMENTS); + aIidList[0] = static_cast(atoi(iidString)); + + if (iidListString != nullptr) + { + // Convert string to an array of integers. + // Integer i is for traverse the iidListString. + // Integer j is for aIidList array offset location. + // First entry of aIidList is for host application iid hence j start from 1. + for (uint8_t i = 0, j = 1; iidListString[i] != '\0' && j < Spinel::kSpinelHeaderMaxNumIid; i++) + { + if (iidListString[i] == ',') + { + j++; + continue; + } + + if (iidListString[i] < '0' || iidListString[i] > '9') + { + DieNow(OT_EXIT_INVALID_ARGUMENTS); + } + else + { + aIidList[j] = iidListString[i] - '0'; + VerifyOrDie(aIidList[j] < Spinel::kSpinelHeaderMaxNumIid, OT_EXIT_INVALID_ARGUMENTS); + } + } + } +#else // !OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + VerifyOrDie(iidString == nullptr, OT_EXIT_INVALID_ARGUMENTS); + VerifyOrDie(iidListString == nullptr, OT_EXIT_INVALID_ARGUMENTS); + aIidList[0] = 0; +#endif // OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +} + } // namespace Posix } // namespace ot diff --git a/src/posix/platform/radio.hpp b/src/posix/platform/radio.hpp index ae0626614..439483f00 100644 --- a/src/posix/platform/radio.hpp +++ b/src/posix/platform/radio.hpp @@ -88,6 +88,7 @@ private: void ProcessMaxPowerTable(const RadioUrl &aRadioUrl); Spinel::SpinelInterface *CreateSpinelInterface(const char *aInterfaceName); + void GetIidListFromRadioUrl(spinel_iid_t (&aIidList)[Spinel::kSpinelHeaderMaxNumIid]); #if OPENTHREAD_POSIX_CONFIG_SPINEL_HDLC_INTERFACE_ENABLE && OPENTHREAD_POSIX_CONFIG_SPINEL_SPI_INTERFACE_ENABLE static constexpr size_t kSpinelInterfaceRawSize = sizeof(ot::Posix::SpiInterface) > sizeof(ot::Posix::HdlcInterface) diff --git a/src/posix/platform/radio_url.cpp b/src/posix/platform/radio_url.cpp index b57e29f81..bd34d9c65 100644 --- a/src/posix/platform/radio_url.cpp +++ b/src/posix/platform/radio_url.cpp @@ -120,7 +120,16 @@ const char *otSysGetRadioUrlHelpString(void) " Disable coex with 0, and enable it with other values.\n" " fem-lnagain[=dbm] Set the Rx LNA gain in dBm of the external FEM.\n" " no-reset Do not send Spinel reset command to RCP on initialization.\n" - " skip-rcp-compatibility-check Skip checking RCP API version and capabilities during initialization.\n"; + " skip-rcp-compatibility-check Skip checking RCP API version and capabilities during initialization.\n" +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + " iid Set the Spinel Interface ID for this process. Valid values are 0-3.\n" + " iid-list List of IIDs a host can subscribe to receive spinel frames other than \n" + " provided in 'iid' argument. If not specified, host will subscribe to \n" + " the interface ID provided in 'iid` argument. Valid values are 0-3. \n" + " Upto three IIDs can be provided with each IID separated by ',' \n" + " e.g. iid-list=1,2,3 \n" +#endif + ; } namespace ot { diff --git a/tests/fuzz/fuzzer_platform.cpp b/tests/fuzz/fuzzer_platform.cpp index 8a64cc8b8..e616fc25e 100644 --- a/tests/fuzz/fuzzer_platform.cpp +++ b/tests/fuzz/fuzzer_platform.cpp @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -230,6 +231,10 @@ OT_TOOL_WEAK void otPlatLog(otLogLevel aLogLevel, otLogRegion aLogRegion, const void otPlatWakeHost(void) {} +otError otPlatMultipanGetActiveInstance(otInstance **) { return OT_ERROR_NOT_IMPLEMENTED; } + +otError otPlatMultipanSetActiveInstance(otInstance *, bool) { return OT_ERROR_NOT_IMPLEMENTED; } + void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64) { OT_UNUSED_VARIABLE(aInstance); diff --git a/tests/unit/CMakeLists.txt b/tests/unit/CMakeLists.txt index 5ffdbd33b..e14e30ad5 100644 --- a/tests/unit/CMakeLists.txt +++ b/tests/unit/CMakeLists.txt @@ -32,27 +32,75 @@ set(COMMON_INCLUDES ${PROJECT_SOURCE_DIR}/src/core ) +set(COMMON_INCLUDES_RCP + ${COMMON_INCLUDES} + ${PROJECT_SOURCE_DIR}/src/core/radio +) + set(COMMON_COMPILE_OPTIONS -DOPENTHREAD_FTD=1 -DOPENTHREAD_SPINEL_CONFIG_OPENTHREAD_MESSAGE_ENABLE=1 ) -add_library(ot-test-platform +set(COMMON_COMPILE_OPTIONS_RCP + -DOPENTHREAD_RADIO=1 + -DOPENTHREAD_SPINEL_CONFIG_OPENTHREAD_MESSAGE_ENABLE=1 + -DOPENTHREAD_CONFIG_PLATFORM_UDP_ENABLE=0 +) + +set(MULTIPAN_RCP_COMPILE_OPTIONS + -DOPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE=1 + -DOPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE=1 + -DOPENTHREAD_CONFIG_LOG_PREPEND_UPTIME=0 + -DOPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE=0 # used to skip backoff and request tx from platform directly. + -DOPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE=1 +) + +add_library(ot-test-platform-ftd + test_platform.cpp + test_util.cpp +) +add_library(ot-test-platform-rcp test_platform.cpp test_util.cpp ) -target_include_directories(ot-test-platform +target_include_directories(ot-test-platform-ftd PRIVATE ${COMMON_INCLUDES} ) -target_compile_options(ot-test-platform +target_include_directories(ot-test-platform-rcp + PRIVATE + ${COMMON_INCLUDES} +) + +target_compile_options(ot-test-platform-ftd PRIVATE ${COMMON_COMPILE_OPTIONS} ) -target_link_libraries(ot-test-platform +target_compile_options(ot-test-platform-rcp + PRIVATE + ${COMMON_COMPILE_OPTIONS_RCP} +) + +if(OT_MULTIPAN_RCP) + target_compile_options(ot-test-platform-rcp + PRIVATE + "-DOPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE=1" + "-DOPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE=1" + "-DOPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE=1" + ) +endif() + +target_link_libraries(ot-test-platform-ftd + PRIVATE + ot-config + ${OT_MBEDTLS} +) + +target_link_libraries(ot-test-platform-rcp PRIVATE ot-config ${OT_MBEDTLS} @@ -61,14 +109,21 @@ target_link_libraries(ot-test-platform set(COMMON_LIBS openthread-spinel-ncp openthread-hdlc - ot-test-platform + ot-test-platform-ftd openthread-ftd - ot-test-platform + ot-test-platform-ftd ${OT_MBEDTLS} ot-config openthread-ftd ) +set(COMMON_LIBS_RCP + ot-test-platform-rcp + openthread-rcp + ${OT_MBEDTLS} + ot-config +) + add_executable(ot-test-aes test_aes.cpp ) @@ -748,6 +803,40 @@ target_link_libraries(ot-test-multicast-listeners-table add_test(NAME ot-test-multicast-listeners-table COMMAND ot-test-multicast-listeners-table) +if(OT_MULTIPAN_RCP) + add_executable(ot-test-multipan-rcp-instances + test_multipan_rcp_instances.cpp + ) + + target_include_directories(ot-test-multipan-rcp-instances + PRIVATE + ${COMMON_INCLUDES_RCP} + ) + + target_compile_options(ot-test-multipan-rcp-instances + PRIVATE + ${COMMON_COMPILE_OPTIONS_RCP} + ${MULTIPAN_RCP_COMPILE_OPTIONS} + ) + + target_compile_definitions(ot-test-multipan-rcp-instances + PRIVATE + "OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE=1" + ) + + target_compile_options(ot-config-radio + INTERFACE + "-DOPENTHREAD_CONFIG_MAC_SOFTWARE_CSMA_BACKOFF_ENABLE=0" # used to skip backoff and request tx from platform directly. + ) + + target_link_libraries(ot-test-multipan-rcp-instances + PRIVATE + ${COMMON_LIBS_RCP} + ) + + add_test(NAME ot-test-multipan-rcp-instances COMMAND ot-test-multipan-rcp-instances) + endif() + add_test(NAME ot-test-nat64 COMMAND ot-test-nat64) add_executable(ot-test-nat64 diff --git a/tests/unit/test_multipan_rcp_instances.cpp b/tests/unit/test_multipan_rcp_instances.cpp new file mode 100644 index 000000000..790e457cf --- /dev/null +++ b/tests/unit/test_multipan_rcp_instances.cpp @@ -0,0 +1,766 @@ +/* + * Copyright (c) 2023, 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 + +#include "common/array.hpp" +#include "common/code_utils.hpp" +#include "instance/instance.hpp" + +#include "ncp/ncp_base.hpp" +#include "openthread/link_raw.h" + +#include "test_platform.h" +#include "test_util.hpp" + +using namespace ot; +using namespace ot::Ncp; + +enum +{ + kTestBufferSize = 800 +}; + +enum +{ + kTestMacScanChannelMask = 0x01 +}; + +OT_TOOL_PACKED_BEGIN +struct RadioMessage +{ + uint8_t mChannel; + uint8_t mPsdu[OT_RADIO_FRAME_MAX_SIZE]; +} OT_TOOL_PACKED_END; + +static struct RadioMessage sDefaultMessages[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM]; +static otRadioFrame sTxFrame[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM]; +static ot::Instance *sInstances[OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM]; +static ot::Instance *sLastInstance; + +otRadioFrame *otPlatRadioGetTransmitBuffer(otInstance *aInstance) +{ + otRadioFrame *frame = nullptr; + + for (size_t i = 0; i < OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; i++) + { + if (sInstances[i] == aInstance || sInstances[i] == nullptr) + { + sTxFrame[i].mPsdu = sDefaultMessages->mPsdu; + frame = &sTxFrame[i]; + + break; + } + } + + return frame; +} + +otError otPlatRadioTransmit(otInstance *aInstance, otRadioFrame *) +{ + sLastInstance = static_cast(aInstance); + return OT_ERROR_NONE; +} + +otError otPlatMultipanGetActiveInstance(otInstance **aInstance) +{ + otError error = OT_ERROR_NOT_IMPLEMENTED; + OT_UNUSED_VARIABLE(aInstance); + +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + *aInstance = sLastInstance; + error = OT_ERROR_NONE; +#endif + + return error; +} + +otError otPlatMultipanSetActiveInstance(otInstance *aInstance, bool aCompletePending) +{ + otError error = OT_ERROR_NOT_IMPLEMENTED; + OT_UNUSED_VARIABLE(aInstance); + OT_UNUSED_VARIABLE(aCompletePending); + +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + VerifyOrExit(sLastInstance != static_cast(aInstance), error = OT_ERROR_ALREADY); + sLastInstance = static_cast(aInstance); + error = OT_ERROR_NONE; +exit: +#endif + + return error; +} + +class TestNcp : public NcpBase +{ +public: + explicit TestNcp(ot::Instance *aInstance) + : mLastHeader(0) + , mLastStatus(0) + , mLastProp(0) + , NcpBase(aInstance) + { + memset(mMsgBuffer, 0, kTestBufferSize); + mTxFrameBuffer.SetFrameAddedCallback(HandleFrameAddedToNcpBuffer, this); + mTxFrameBuffer.SetFrameRemovedCallback(nullptr, this); + }; + + explicit TestNcp(ot::Instance **aInstances, uint8_t aCount) + : mLastHeader(0) + , mLastStatus(0) + , mLastProp(0) + , NcpBase(aInstances, aCount) + { + memset(mMsgBuffer, 0, kTestBufferSize); + mTxFrameBuffer.SetFrameAddedCallback(HandleFrameAddedToNcpBuffer, this); + mTxFrameBuffer.SetFrameRemovedCallback(nullptr, this); + }; + + static void HandleFrameAddedToNcpBuffer(void *aContext, + Spinel::Buffer::FrameTag aTag, + Spinel::Buffer::Priority aPriority, + Spinel::Buffer *aBuffer) + { + OT_UNUSED_VARIABLE(aTag); + OT_UNUSED_VARIABLE(aPriority); + + static_cast(aContext)->HandleFrameAddedToNcpBuffer(aBuffer); + } + + void HandleFrameAddedToNcpBuffer(Spinel::Buffer *aBuffer) + { + static const size_t display_size = 64; + + memset(mMsgBuffer, 0, kTestBufferSize); + SuccessOrQuit(aBuffer->OutFrameBegin()); + aBuffer->OutFrameRead(kTestBufferSize, mMsgBuffer); + SuccessOrQuit(aBuffer->OutFrameRemove()); + + // DumpBuffer("Received Buffer", mMsgBuffer, display_size); + + updateSpinelStatus(); + } + + void Receive(uint8_t *aBuffer, size_t bufferSize) { HandleReceive(aBuffer, static_cast(bufferSize)); } + + void processTransmit() + { + uint8_t iid = SPINEL_HEADER_GET_IID(mLastHeader); + + LinkRawTransmitDone(iid, &sTxFrame[iid], nullptr, OT_ERROR_NONE); + }; + + void updateSpinelStatus() + { + Spinel::Decoder decoder; + + uint8_t header; + unsigned int command; + unsigned int propKey; + unsigned int status; + + decoder.Init(mMsgBuffer, kTestBufferSize); + + SuccessOrQuit(decoder.ReadUint8(mLastHeader)); + SuccessOrQuit(decoder.ReadUintPacked(command)); + SuccessOrQuit(decoder.ReadUintPacked(propKey)); + SuccessOrQuit(decoder.ReadUintPacked(status)); + + mLastStatus = static_cast(status); + mLastProp = static_cast(propKey); + } + + uint32_t getSpinelStatus() const { return mLastStatus; } + uint32_t getSpinelProp() const { return mLastProp; } + + uint8_t getLastIid() const + { + /* Return as SPINEL_HEADER_IID_N format without shift */ + return SPINEL_HEADER_IID_MASK & mLastHeader; + } + + uint8_t getLastTid() { return SPINEL_HEADER_GET_TID(mLastHeader); } + + bool gotResponse(uint8_t aIid, uint8_t aTid) { return ((aIid == getLastIid()) && (aTid == getLastTid())); } + +private: + uint8_t mLastHeader; + uint32_t mLastStatus; + uint32_t mLastProp; + uint8_t mMsgBuffer[kTestBufferSize]; +}; + +class TestHost +{ +public: + TestHost(TestNcp *aNcp, uint8_t aIid) + : mNcp(aNcp) + , mIid(aIid) + , mTid(0) + , mLastTxTid(0) + , mBuffer(mBuf, kTestBufferSize) + , mEncoder(mBuffer) + , mOffset(0) + { + memset(mBuf, 0, kTestBufferSize); + }; + + void createLinkEnableFrame(bool isEnabled) + { + startFrame(SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_PHY_ENABLED); + SuccessOrQuit(mEncoder.WriteBool(isEnabled)); + endFrame("Enable Frame"); + } + + void createTransmitFrame() + { + startFrame(SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_STREAM_RAW); + + SuccessOrQuit(mEncoder.WriteDataWithLen(sTxFrame[mIid].mPsdu, sTxFrame[mIid].mLength)); + SuccessOrQuit(mEncoder.WriteUint8(sTxFrame[mIid].mChannel)); + SuccessOrQuit(mEncoder.WriteUint8(sTxFrame[mIid].mInfo.mTxInfo.mMaxCsmaBackoffs)); + SuccessOrQuit(mEncoder.WriteUint8(sTxFrame[mIid].mInfo.mTxInfo.mMaxFrameRetries)); + SuccessOrQuit(mEncoder.WriteBool(sTxFrame[mIid].mInfo.mTxInfo.mCsmaCaEnabled)); + SuccessOrQuit(mEncoder.WriteBool(sTxFrame[mIid].mInfo.mTxInfo.mIsHeaderUpdated)); + SuccessOrQuit(mEncoder.WriteBool(sTxFrame[mIid].mInfo.mTxInfo.mIsARetx)); + SuccessOrQuit(mEncoder.WriteBool(sTxFrame[mIid].mInfo.mTxInfo.mIsSecurityProcessed)); + SuccessOrQuit(mEncoder.WriteUint32(sTxFrame[mIid].mInfo.mTxInfo.mTxDelay)); + SuccessOrQuit(mEncoder.WriteUint32(sTxFrame[mIid].mInfo.mTxInfo.mTxDelayBaseTime)); + + endFrame("Transmit Frame"); + } + + void createSwitchoverRequest(uint8_t aIid, bool aForce) + { + startFrame(SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE); + SuccessOrQuit(mEncoder.WriteUint8(aIid | (aForce ? 0 : (1 << SPINEL_MULTIPAN_INTERFACE_SOFT_SWITCH_SHIFT)))); + endFrame("Interface Switch Request Frame"); + } + + void createReadStatusFrame() + { + startFrame(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_LAST_STATUS); + endFrame("Read Status Frame"); + } + + void enableRawLink() + { + static const bool isLinkEnabled = true; + createLinkEnableFrame(isLinkEnabled); + sendToRcp(); + } + + void disableRawLink() + { + static const bool isLinkEnabled = false; + createLinkEnableFrame(isLinkEnabled); + sendToRcp(); + } + + spinel_status_t startTransmit() + { + mLastTxTid = mTid; + createTransmitFrame(); + sendToRcp(); + prepareResponse(mLastTxTid); + return static_cast(mNcp->getSpinelStatus()); + }; + + spinel_status_t requestSwitchover(uint8_t aIid, bool aForce) + { + mLastTxTid = mTid; + createSwitchoverRequest(aIid, aForce); + sendToRcp(); + prepareResponse(mLastTxTid); + return static_cast(mNcp->getSpinelStatus()); + }; + + void getCommandStatus() + { + createReadStatusFrame(); + sendToRcp(); + } + + void finishTransmit() + { + /* Reset instance submac state to sleep by resetting link + This is needed for a second transmit command to succeed + as the HandleTimer method will not be called to reset the submac */ + disableRawLink(); + enableRawLink(); + + /* Proceed with transmit done callback from ncp */ + mNcp->processTransmit(); + }; + + uint8_t getLastTransmitTid(void) { return mLastTxTid; } + +private: + void startFrame(unsigned int aCommand, spinel_prop_key_t aKey) + { + uint8_t spinelHeader = SPINEL_HEADER_FLAG | mIid | mTid; + + SuccessOrQuit(mEncoder.BeginFrame(Spinel::Buffer::kPriorityLow)); + SuccessOrQuit(mEncoder.WriteUint8(spinelHeader)); + SuccessOrQuit(mEncoder.WriteUintPacked(aCommand)); + SuccessOrQuit(mEncoder.WriteUintPacked(aKey)); + } + + void endFrame(const char *aTextMessage) + { + static const uint16_t display_length = 64; + SuccessOrQuit(mEncoder.EndFrame()); + // DumpBuffer(aTextMessage, mBuf, display_length); + } + + void sendToRcp() + { + static const uint8_t data_offset = 2; + size_t frame_len = mBuffer.OutFrameGetLength(); + + mOffset += data_offset; + + mNcp->Receive(mBuf + mOffset, frame_len); + + mTid = SPINEL_GET_NEXT_TID(mTid); + SuccessOrQuit(mBuffer.OutFrameRemove()); + + mOffset += frame_len; + mOffset %= kTestBufferSize; + } + + void prepareResponse(uint8_t aTid) + { + /* Some spinel commands immediately send queued responses when command is complete + while others require a separate command to the ncp in order to receive the response. + If a response is needed and not immediately received. Issue a command to update the status. */ + + if (!mNcp->gotResponse(mIid, aTid)) + { + getCommandStatus(); + } + } + + TestNcp *mNcp; + uint8_t mIid; + uint8_t mTid; + uint8_t mLastTxTid; + uint8_t mBuf[kTestBufferSize]; + Spinel::Buffer mBuffer; + Spinel::Encoder mEncoder; + size_t mOffset; +}; + +void InitInstances(void) +{ +#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE + for (size_t i = 0; i < OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; i++) + { + sInstances[i] = testInitAdditionalInstance(i); + VerifyOrQuit(sInstances[i] != nullptr); + } +#endif +} + +void FreeInstances(void) +{ + for (size_t i = 0; i < OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM; i++) + { + if (sInstances[i] != nullptr) + { + testFreeInstance(sInstances[i]); + sInstances[i] = nullptr; + } + } +} + +void TestNcpBaseTransmitWithLinkRawDisabled(void) +{ + printf("\tTransmit With Link Raw Disabled"); + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host1(&ncp, SPINEL_HEADER_IID_0); + TestHost host2(&ncp, SPINEL_HEADER_IID_1); + TestHost host3(&ncp, SPINEL_HEADER_IID_2); + + host1.disableRawLink(); + host2.disableRawLink(); + host3.disableRawLink(); + + /* Test that the response status is Invalid State when transmit is skipped due to disabled link */ + VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_INVALID_STATE); + VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_INVALID_STATE); + VerifyOrQuit(host3.startTransmit() == SPINEL_STATUS_INVALID_STATE); + + FreeInstances(); + printf(" - PASS\n"); +} + +void TestNcpBaseTransmitWithLinkRawEnabled(void) +{ + printf("\tTransmit With Link Raw Enabled"); + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host(&ncp, SPINEL_HEADER_IID_0); + + host.enableRawLink(); + + /* Test that the response status is OK when transmit is started successfully */ + VerifyOrQuit(host.startTransmit() == SPINEL_STATUS_OK); + + host.finishTransmit(); + + FreeInstances(); + printf(" - PASS\n"); +} + +void TestNcpBaseTransmitWithIncorrectLinkRawEnabled(void) +{ + printf("\tTransmit With Incorrect Link Raw Enabled"); + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host1(&ncp, SPINEL_HEADER_IID_0); + TestHost host2(&ncp, SPINEL_HEADER_IID_1); + + host1.disableRawLink(); + host2.enableRawLink(); + + /* Test that Invalid State is reported when different endpoint was enabled */ + VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_INVALID_STATE); + + /* Test that status is OK when transmitting on the proper interface */ + VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK); + + host1.finishTransmit(); + + FreeInstances(); + printf(" - PASS\n"); +} + +void TestNcpBaseTransmitOnBoth(void) +{ + printf("\tTransmit on both interfaces"); + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host1(&ncp, SPINEL_HEADER_IID_0); + TestHost host2(&ncp, SPINEL_HEADER_IID_1); + + host1.enableRawLink(); + host2.enableRawLink(); + + VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_OK); + VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK); + + host1.finishTransmit(); + host2.finishTransmit(); + + FreeInstances(); + printf(" - PASS\n"); +} + +void TestNcpBaseDifferentInstanceCall(void) +{ + printf("\tTransmit on both interfaces - verify instances used"); + + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host1(&ncp, SPINEL_HEADER_IID_0); + TestHost host2(&ncp, SPINEL_HEADER_IID_1); + + sLastInstance = nullptr; + + host1.enableRawLink(); + host2.enableRawLink(); + + VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_OK); + VerifyOrQuit(sLastInstance != nullptr); + VerifyOrQuit(sLastInstance == sInstances[0]); + + VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK); + VerifyOrQuit(sLastInstance != nullptr); + VerifyOrQuit(sLastInstance == sInstances[1]); + + host1.finishTransmit(); + host2.finishTransmit(); + + /* Test reverse order of calls to make sure it is not just a fixed order */ + sLastInstance = nullptr; + VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK); + VerifyOrQuit(sLastInstance != nullptr); + VerifyOrQuit(sLastInstance == sInstances[1]); + + VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_OK); + VerifyOrQuit(sLastInstance != nullptr); + VerifyOrQuit(sLastInstance == sInstances[0]); + + host1.finishTransmit(); + host2.finishTransmit(); + + printf(" - PASS\n"); +} + +void TestNcpBaseTransmitDoneInterface(void) +{ + printf("\tTransmit on both interfaces - verify transmit done IID"); + + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host1(&ncp, SPINEL_HEADER_IID_0); + TestHost host2(&ncp, SPINEL_HEADER_IID_1); + + sLastInstance = nullptr; + + host1.enableRawLink(); + host2.enableRawLink(); + + VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_OK); + VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK); + + otPlatRadioTxDone(sInstances[0], &sTxFrame[0], nullptr, OT_ERROR_NONE); + VerifyOrQuit(ncp.gotResponse(SPINEL_HEADER_IID_0, host1.getLastTransmitTid())); + + otPlatRadioTxDone(sInstances[1], &sTxFrame[1], nullptr, OT_ERROR_NONE); + VerifyOrQuit(ncp.gotResponse(SPINEL_HEADER_IID_1, host2.getLastTransmitTid())); + + /* Test reverse order of tx processing */ + VerifyOrQuit(host1.startTransmit() == SPINEL_STATUS_OK); + VerifyOrQuit(host2.startTransmit() == SPINEL_STATUS_OK); + + otPlatRadioTxDone(sInstances[1], &sTxFrame[1], nullptr, OT_ERROR_NONE); + VerifyOrQuit(ncp.gotResponse(SPINEL_HEADER_IID_1, host2.getLastTransmitTid())); + + otPlatRadioTxDone(sInstances[0], &sTxFrame[0], nullptr, OT_ERROR_NONE); + VerifyOrQuit(ncp.gotResponse(SPINEL_HEADER_IID_0, host1.getLastTransmitTid())); + + printf(" - PASS\n"); +} + +void TestNcpBaseReceive(void) +{ + printf("\tReceive on a single interface"); + + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host1(&ncp, SPINEL_HEADER_IID_0); + + host1.enableRawLink(); + + otPlatRadioReceiveDone(sInstances[0], &sTxFrame[0], OT_ERROR_NONE); + + VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_STREAM_RAW); + VerifyOrQuit(ncp.getLastTid() == 0); + VerifyOrQuit(ncp.getLastIid() == SPINEL_HEADER_IID_0); + + printf(" - PASS\n"); +} + +void TestNcpBaseReceiveOnTwoInterfaces(void) +{ + printf("\tReceive on both interfaces"); + + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host1(&ncp, SPINEL_HEADER_IID_0); + TestHost host2(&ncp, SPINEL_HEADER_IID_1); + + sLastInstance = nullptr; + + host1.enableRawLink(); + host2.enableRawLink(); + + otPlatRadioReceiveDone(sInstances[1], &sTxFrame[1], OT_ERROR_NONE); + + VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_STREAM_RAW); + VerifyOrQuit(ncp.getLastTid() == 0); + VerifyOrQuit(ncp.getLastIid() == SPINEL_HEADER_IID_1); + + otPlatRadioReceiveDone(sInstances[0], &sTxFrame[0], OT_ERROR_NONE); + + VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_STREAM_RAW); + VerifyOrQuit(ncp.getLastTid() == 0); + VerifyOrQuit(ncp.getLastIid() == SPINEL_HEADER_IID_0); + + /* reverse order */ + otPlatRadioReceiveDone(sInstances[0], &sTxFrame[0], OT_ERROR_NONE); + + VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_STREAM_RAW); + VerifyOrQuit(ncp.getLastTid() == 0); + VerifyOrQuit(ncp.getLastIid() == SPINEL_HEADER_IID_0); + + otPlatRadioReceiveDone(sInstances[1], &sTxFrame[1], OT_ERROR_NONE); + + VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_STREAM_RAW); + VerifyOrQuit(ncp.getLastTid() == 0); + VerifyOrQuit(ncp.getLastIid() == SPINEL_HEADER_IID_1); + + printf(" - PASS\n"); +} + +void TestNcpBaseSwitchoverRequest(void) +{ + printf("\tSwitchover requests from different interfaces"); + + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host1(&ncp, SPINEL_HEADER_IID_0); + TestHost host2(&ncp, SPINEL_HEADER_IID_1); + TestHost host3(&ncp, SPINEL_HEADER_IID_2); + + sLastInstance = nullptr; + + host1.enableRawLink(); + host2.enableRawLink(); + host3.enableRawLink(); + + VerifyOrQuit(host1.requestSwitchover(0, true) == 0); + VerifyOrQuit(sLastInstance == sInstances[0]); + VerifyOrQuit(host1.requestSwitchover(1, true) == 1); + VerifyOrQuit(sLastInstance == sInstances[1]); + VerifyOrQuit(host1.requestSwitchover(2, true) == 2); + VerifyOrQuit(sLastInstance == sInstances[2]); + VerifyOrQuit(host2.requestSwitchover(0, true) == 0); + VerifyOrQuit(sLastInstance == sInstances[0]); + VerifyOrQuit(host2.requestSwitchover(1, true) == 1); + VerifyOrQuit(sLastInstance == sInstances[1]); + VerifyOrQuit(host2.requestSwitchover(2, true) == 2); + VerifyOrQuit(sLastInstance == sInstances[2]); + VerifyOrQuit(host3.requestSwitchover(0, true) == 0); + VerifyOrQuit(sLastInstance == sInstances[0]); + VerifyOrQuit(host3.requestSwitchover(1, true) == 1); + VerifyOrQuit(sLastInstance == sInstances[1]); + VerifyOrQuit(host3.requestSwitchover(2, true) == 2); + VerifyOrQuit(sLastInstance == sInstances[2]); + + printf(" - PASS\n"); +} + +void TestNcpBaseSwitchoverRequestFail(void) +{ + printf("\tSwitchover requests Fail - same interface"); + + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host1(&ncp, SPINEL_HEADER_IID_0); + TestHost host2(&ncp, SPINEL_HEADER_IID_1); + + sLastInstance = nullptr; + + host1.enableRawLink(); + host2.enableRawLink(); + + VerifyOrQuit(host1.requestSwitchover(0, true) == 0); + VerifyOrQuit(sLastInstance == sInstances[0]); + + VerifyOrQuit(host1.requestSwitchover(0, true) == SPINEL_STATUS_ALREADY); + VerifyOrQuit(sLastInstance == sInstances[0]); + + VerifyOrQuit(host2.requestSwitchover(0, true) == SPINEL_STATUS_ALREADY); + VerifyOrQuit(sLastInstance == sInstances[0]); + + printf(" - PASS\n"); +} + +void TestNcpBaseSwitchoverResponse(void) +{ + printf("\tSwitchover responses"); + + InitInstances(); + + TestNcp ncp(sInstances, OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_NUM); + TestHost host1(&ncp, SPINEL_HEADER_IID_0); + TestHost host2(&ncp, SPINEL_HEADER_IID_1); + + sLastInstance = nullptr; + + host1.enableRawLink(); + host2.enableRawLink(); + + VerifyOrQuit(host1.requestSwitchover(0, true) == 0); + VerifyOrQuit(sLastInstance == sInstances[0]); + + otPlatMultipanSwitchoverDone(sLastInstance, true); + + VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_LAST_STATUS); + VerifyOrQuit(ncp.getLastTid() == 0); + VerifyOrQuit(ncp.getLastIid() == OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID); + VerifyOrQuit(ncp.getSpinelStatus() == SPINEL_STATUS_SWITCHOVER_DONE); + + VerifyOrQuit(host1.requestSwitchover(1, true) == 1); + VerifyOrQuit(sLastInstance == sInstances[1]); + + otPlatMultipanSwitchoverDone(sLastInstance, false); + + VerifyOrQuit(ncp.getSpinelProp() == SPINEL_PROP_LAST_STATUS); + VerifyOrQuit(ncp.getLastTid() == 0); + VerifyOrQuit(ncp.getLastIid() == OPENTHREAD_SPINEL_CONFIG_BROADCAST_IID); + VerifyOrQuit(ncp.getSpinelStatus() == SPINEL_STATUS_SWITCHOVER_FAILED); + + printf(" - PASS\n"); +} + +/// +int main(void) +{ +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE && (OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE) + printf("Executing Transmit Tests\n"); + TestNcpBaseTransmitWithLinkRawDisabled(); + TestNcpBaseTransmitWithLinkRawEnabled(); + TestNcpBaseTransmitWithIncorrectLinkRawEnabled(); + TestNcpBaseTransmitOnBoth(); + TestNcpBaseDifferentInstanceCall(); + TestNcpBaseTransmitDoneInterface(); + printf("Transmit Tests - PASS\n"); + + printf("Executing Receive Tests\n"); + TestNcpBaseReceive(); + TestNcpBaseReceiveOnTwoInterfaces(); + printf("Receive Tests - PASS\n"); + + printf("Executing Interface Switching Tests\n"); + TestNcpBaseSwitchoverRequest(); + TestNcpBaseSwitchoverRequestFail(); + TestNcpBaseSwitchoverResponse(); + printf("Executing Interface Switching Tests - PASS\n"); + + printf("\nAll tests passed\n"); + +#else + printf("MULTIPAN_RCP feature and RADIO/LINK_RAW option are not enabled\n"); +#endif + return 0; +} diff --git a/tests/unit/test_platform.cpp b/tests/unit/test_platform.cpp index 21718f1d3..8c339d443 100644 --- a/tests/unit/test_platform.cpp +++ b/tests/unit/test_platform.cpp @@ -49,6 +49,9 @@ ot::Instance *testInitInstance(void) otInstance *instance = nullptr; #if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE +#if OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE + instance = otInstanceInitMultiple(0); +#else size_t instanceBufferLength = 0; uint8_t *instanceBuffer = nullptr; @@ -62,6 +65,7 @@ ot::Instance *testInitInstance(void) // Initialize OpenThread with the buffer instance = otInstanceInit(instanceBuffer, &instanceBufferLength); +#endif #else instance = otInstanceInitSingle(); #endif @@ -69,11 +73,22 @@ ot::Instance *testInitInstance(void) return static_cast(instance); } +#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE +ot::Instance *testInitAdditionalInstance(uint8_t id) +{ + otInstance *instance = nullptr; + + instance = otInstanceInitMultiple(id); + + return static_cast(instance); +} +#endif + void testFreeInstance(otInstance *aInstance) { otInstanceFinalize(aInstance); -#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE +#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && !OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE free(aInstance); #endif } @@ -116,6 +131,10 @@ OT_TOOL_WEAK uint32_t otPlatAlarmMicroGetNow(void) return (uint32_t)((tv.tv_sec * 1000000) + tv.tv_usec + 123456); } +OT_TOOL_WEAK otError otPlatMultipanGetActiveInstance(otInstance **) { return OT_ERROR_NOT_IMPLEMENTED; } + +OT_TOOL_WEAK otError otPlatMultipanSetActiveInstance(otInstance *, bool) { return OT_ERROR_NOT_IMPLEMENTED; } + OT_TOOL_WEAK void otPlatRadioGetIeeeEui64(otInstance *, uint8_t *) {} OT_TOOL_WEAK void otPlatRadioSetPanId(otInstance *, uint16_t) {} @@ -229,6 +248,8 @@ OT_TOOL_WEAK otError otPlatResetToBootloader(otInstance *) { return OT_ERROR_NOT OT_TOOL_WEAK otPlatResetReason otPlatGetResetReason(otInstance *) { return OT_PLAT_RESET_REASON_POWER_ON; } +OT_TOOL_WEAK void otPlatWakeHost(void) {} + OT_TOOL_WEAK void otPlatLog(otLogLevel, otLogRegion, const char *, ...) {} OT_TOOL_WEAK void otPlatSettingsInit(otInstance *, const uint16_t *, uint16_t) {} @@ -627,4 +648,38 @@ void otPlatDnsCancelUpstreamQuery(otInstance *aInstance, otPlatDnsUpstreamQuery } #endif +OT_TOOL_WEAK otError otPlatRadioGetCcaEnergyDetectThreshold(otInstance *, int8_t *) { return OT_ERROR_NONE; } + +OT_TOOL_WEAK otError otPlatRadioGetCoexMetrics(otInstance *, otRadioCoexMetrics *) { return OT_ERROR_NONE; } + +OT_TOOL_WEAK otError otPlatRadioGetTransmitPower(otInstance *, int8_t *) { return OT_ERROR_NONE; } + +OT_TOOL_WEAK bool otPlatRadioIsCoexEnabled(otInstance *) { return true; } + +OT_TOOL_WEAK otError otPlatRadioSetCoexEnabled(otInstance *, bool) { return OT_ERROR_NOT_IMPLEMENTED; } + +#if OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE +OT_TOOL_WEAK otError otPlatRadioSetChannelTargetPower(otInstance *aInstance, uint8_t aChannel, int16_t aTargetPower) +{ + return OT_ERROR_NONE; +} + +OT_TOOL_WEAK otError otPlatRadioAddCalibratedPower(otInstance *aInstance, + uint8_t aChannel, + int16_t aActualPower, + const uint8_t *aRawPowerSetting, + uint16_t aRawPowerSettingLength) +{ + return OT_ERROR_NONE; +} + +OT_TOOL_WEAK otError otPlatRadioClearCalibratedPowers(otInstance *aInstance) { return OT_ERROR_NONE; } +#endif // OPENTHREAD_CONFIG_PLATFORM_POWER_CALIBRATION_ENABLE + +#if OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL +OT_TOOL_WEAK otPlatMcuPowerState otPlatGetMcuPowerState(otInstance *aInstance) { return OT_PLAT_MCU_POWER_STATE_ON; } + +OT_TOOL_WEAK otError otPlatSetMcuPowerState(otInstance *aInstance, otPlatMcuPowerState aState) { return OT_ERROR_NONE; } +#endif // OPENTHREAD_CONFIG_NCP_ENABLE_MCU_POWER_STATE_CONTROL + } // extern "C" diff --git a/tests/unit/test_platform.h b/tests/unit/test_platform.h index 76a72ba09..97a9d79d9 100644 --- a/tests/unit/test_platform.h +++ b/tests/unit/test_platform.h @@ -38,6 +38,7 @@ #include #include #include +#include #include #include "common/code_utils.hpp" @@ -46,6 +47,9 @@ #include "test_util.h" ot::Instance *testInitInstance(void); -void testFreeInstance(otInstance *aInstance); +#if OPENTHREAD_CONFIG_MULTIPLE_INSTANCE_ENABLE && OPENTHREAD_CONFIG_MULTIPLE_STATIC_INSTANCE_ENABLE +ot::Instance *testInitAdditionalInstance(uint8_t id); +#endif +void testFreeInstance(otInstance *aInstance); #endif // TEST_PLATFORM_H