From 834c8cbc8e6b31cf63a7d6d8ca39d86eda3e1cc2 Mon Sep 17 00:00:00 2001 From: Marek Porwisz <59468987+MarekPorwisz@users.noreply.github.com> Date: Wed, 29 Nov 2023 01:16:37 +0100 Subject: [PATCH] [spinel] add support for multiple spinel interfaces (#9360) This feature allows the RCP to support multiple host stacks on different PANs by making use of the spinel Interface ID. Created unit tests for testing multipan feature with multiple ot-instance support. Based on Si-Labs PR #8914 by @parag-silabs, but a little different approach. Instead of handling everything by a single sub-mac instance, multiple OpenThread instances are created on RCP side that map to different IID. Thanks to this there are separate data kept for each interface. Platform is able to determine interface by ot instance pointer passed as an argument to most of the API functions. Tx/scan queue was removed as it is possible to request transmission in parallel, it is up to the platform to decide if it should fail or queue second tx or it has two radios available. NOTE: Platform needs to provide different otRadioFrame of each instance and the processing needs to take into account the instance being used. Signed-off-by: Marek Porwisz --- .github/workflows/unit.yml | 4 + doc/ot_api_doc.h | 1 + etc/cmake/options.cmake | 2 +- examples/apps/ncp/main.c | 32 +- examples/apps/ncp/ncp.c | 13 + examples/platforms/simulation/CMakeLists.txt | 1 + examples/platforms/simulation/multipan.c | 66 ++ include/openthread/BUILD.gn | 1 + include/openthread/instance.h | 19 +- include/openthread/ncp.h | 10 + include/openthread/platform/multipan.h | 145 ++++ src/core/api/instance_api.cpp | 10 + src/core/config/misc.h | 30 + src/core/instance/instance.cpp | 29 + src/core/instance/instance.hpp | 15 + src/lib/spinel/openthread-spinel-config.h | 11 + src/lib/spinel/radio_spinel.cpp | 212 +++-- src/lib/spinel/radio_spinel.hpp | 211 ++++- src/lib/spinel/spinel.c | 2 + src/lib/spinel/spinel.h | 48 +- src/lib/spinel/spinel_interface.hpp | 24 +- src/ncp/BUILD.gn | 1 + src/ncp/CMakeLists.txt | 10 + src/ncp/example_vendor_hook.cpp | 29 + src/ncp/multipan_platform.cpp | 101 +++ src/ncp/ncp_base.cpp | 162 +++- src/ncp/ncp_base.hpp | 116 ++- src/ncp/ncp_base_dispatcher.cpp | 6 + src/ncp/ncp_base_radio.cpp | 102 ++- src/ncp/ncp_hdlc.cpp | 45 ++ src/ncp/ncp_hdlc.hpp | 13 + src/posix/platform/CMakeLists.txt | 7 + src/posix/platform/radio.cpp | 70 +- src/posix/platform/radio.hpp | 1 + src/posix/platform/radio_url.cpp | 11 +- tests/fuzz/fuzzer_platform.cpp | 5 + tests/unit/CMakeLists.txt | 101 ++- tests/unit/test_multipan_rcp_instances.cpp | 766 +++++++++++++++++++ tests/unit/test_platform.cpp | 57 +- tests/unit/test_platform.h | 6 +- 40 files changed, 2324 insertions(+), 171 deletions(-) create mode 100644 examples/platforms/simulation/multipan.c create mode 100644 include/openthread/platform/multipan.h create mode 100644 src/ncp/multipan_platform.cpp create mode 100644 tests/unit/test_multipan_rcp_instances.cpp diff --git a/.github/workflows/unit.yml b/.github/workflows/unit.yml index 638a80f282d..1df2df5376b 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 d6ce51e47f5..84e48c8005d 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 5f5d9686365..3b5a155d3ec 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 ec3f8dc96d0..5f59261e80c 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 @@ int main(int argc, char *argv[]) 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 @@ int main(int argc, char *argv[]) #endif assert(instance); +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE + otAppNcpInitMulti(instances, ENDPOINT_CT); +#else otAppNcpInit(instance); +#endif while (!otSysPseudoResetWasRequested()) { @@ -103,7 +129,7 @@ int main(int argc, char *argv[]) } 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 df6529a3aca..38c95b42dc2 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 c99597d4a07..439fcb9d46c 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 00000000000..1c173dfe03f --- /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 5bc1c09677b..e4760c29c08 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 18e23d2e68f..7094bf408fe 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 7e07fa29013..8c810fc84c7 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 00000000000..69cd9839c78 --- /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 6d51d3c3986..5d6d5971e03 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 df9fd28fa40..c9be7ec5e44 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 872718fafc0..706671a97f7 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 4d1a3255b96..596a5c68cd4 100644 --- a/src/core/instance/instance.hpp +++ b/src/core/instance/instance.hpp @@ -186,6 +186,21 @@ class Instance : public otInstance, private NonCopyable */ 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 086a7584234..6b3316ac19d 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 ffca5b277ee..2cd1deaff8c 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 @@ void RadioSpinel::Init(SpinelInterface &aSpinelInterface, bool aResetRadio, bool 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 @@ otError RadioSpinel::SetMacFrameCounter(uint32_t aMacFrameCounter, bool aSetIfLa 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 69af0a55724..c6eb1fdaafd 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 @@ class RadioSpinel * @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, + 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 Init(SpinelInterface &aSpinelInterface, bool aResetRadio, bool aSkipRcpCompatibilityCheck); + void SetCallbacks(const struct RadioSpinelCallbacks &aCallbacks); /** * Deinitialize this radio transceiver. @@ -242,7 +354,7 @@ class RadioSpinel * @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 @@ class RadioSpinel * @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 @@ class RadioSpinel 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 @@ class RadioSpinel 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 @@ class RadioSpinel 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 @@ class RadioSpinel 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 @@ class RadioSpinel 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 @@ class RadioSpinel 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 963f2762636..82ea5555626 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 a4802af9b41..5d8094db49f 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 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_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_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 432b8b7ee31..10d0c96376a 100644 --- a/src/lib/spinel/spinel_interface.hpp +++ b/src/lib/spinel/spinel_interface.hpp @@ -175,9 +175,27 @@ class SpinelInterface */ 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 2adfdecaf58..2bb99814a51 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 84062470b91..25d7e2de13e 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 62c0b6aaa24..116b8493c55 100644 --- a/src/ncp/example_vendor_hook.cpp +++ b/src/ncp/example_vendor_hook.cpp @@ -141,6 +141,12 @@ class NcpVendorUart : public ot::Ncp::NcpHdlc : 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 00000000000..92374e3225d --- /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 288373fc772..e0b4c1881c0 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 @@ template <> otError NcpBase::HandlePropertySet( 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 b618101c358..7b8d94f879d 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 @@ class NcpBase { 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 @@ class NcpBase */ 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 @@ class NcpBase */ 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 @@ class NcpBase */ 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 @@ class NcpBase #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 @@ class NcpBase 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 @@ class NcpBase 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 @@ class NcpBase uint8_t mTxBuffer[kTxBufferSize]; - spinel_tid_t mNextExpectedTid; + spinel_tid_t mNextExpectedTid[kSpinelInterfaceCount]; uint8_t mResponseQueueHead; uint8_t mResponseQueueTail; @@ -577,7 +666,7 @@ class NcpBase bool mAllowLocalNetworkDataChange; bool mRequireJoinExistingNetwork; - bool mIsRawStreamEnabled; + bool mIsRawStreamEnabled[kSpinelInterfaceCount]; bool mPcapEnabled; bool mDisableStreamWrite; bool mShouldEmitChildTableUpdate; @@ -591,11 +680,12 @@ class NcpBase #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 568b95964ad..e653f83115f 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 428037e4bce..cad3ef52ddc 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 @@ otError NcpBase::PackRadioFrame(otRadioFrame *aFrame, otError aError) 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 @@ void NcpBase::LinkRawReceiveDone(otRadioFrame *aFrame, otError aError) 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 @@ void NcpBase::LinkRawTransmitDone(otRadioFrame *aFrame, otRadioFrame *aAckFrame, 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) return error; } +#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE +template <> 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 @@ otError NcpBase::DecodeStreamRawTxRequest(otRadioFrame &aFrame) 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 @@ template <> otError NcpBase::HandlePropertySet 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 59da4249646..9950de8a357 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 d99ef96ded3..751611db22f 100644 --- a/src/ncp/ncp_hdlc.hpp +++ b/src/ncp/ncp_hdlc.hpp @@ -59,6 +59,19 @@ class NcpHdlc : public NcpBase */ 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 dfc62408eba..3934d0ccb52 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 86901dd7df1..491c6db1f73 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 @@ void Radio::ProcessMaxPowerTable(const RadioUrl &aRadioUrl) #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 ae06266142e..439483f00e1 100644 --- a/src/posix/platform/radio.hpp +++ b/src/posix/platform/radio.hpp @@ -88,6 +88,7 @@ class Radio 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 b57e29f81d3..bd34d9c65cc 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 8a64cc8b8a1..e616fc25e00 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 5ffdbd33ba3..e14e30ad5bd 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 00000000000..790e457cf39 --- /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 21718f1d385..8c339d44368 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 76a72ba0904..97a9d79d999 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