Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Openthread upmerge to 4ed44bc #126

Merged
merged 40 commits into from
Dec 7, 2023
Merged
Changes from 1 commit
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
a184dc9
[platform] fix compiling issue when use `SuccessOrDie` (#9640)
zwx1995esp Nov 24, 2023
bcdd5b0
[typo] fix error message of link metrics option dependency (#9646)
Irving-cl Nov 24, 2023
0044def
[mlr-manager] define `AddressArray` which uses `Array<Ip6::Address>` …
abtink Nov 24, 2023
6c84cca
[ip6] update Ip6::SelectSourceAddress() to prefer non-deprecated addr…
abtink Nov 24, 2023
f3602b4
[config] define `OPENTHREAD_CONFIG_TLS_UPDATE` in all cases (#9630)
tima-q Nov 24, 2023
a8afffb
[mlr] use shorter variable/method names (#9635)
abtink Nov 25, 2023
d6827ca
github-actions: bump step-security/harden-runner from 2.6.0 to 2.6.1 …
dependabot[bot] Nov 27, 2023
b07b176
[posix] fix unused private variables (#9644)
bukepo Nov 28, 2023
90d16d9
[csl] fix `TimerMicro::GetNow()` call in `sub_mac.cpp` (#9639)
maciejbaczmanski Nov 28, 2023
4b593f9
[tests] fix the check of external routes in `test_multi_thread_networ…
superwhd Nov 28, 2023
dc616d6
[dhcp6-pd] stop `PrefixManager` when `RoutingManager` is stopped (#9608)
erjiaqing Nov 28, 2023
c805678
[dataset] simplify saving/reading of TLVs in/from secure storage (#9626)
abtink Nov 28, 2023
f859987
[spinel] fix compiling issue of enum multiplication (#9649)
gytxxsy Nov 28, 2023
834c8cb
[spinel] add support for multiple spinel interfaces (#9360)
MarekPorwisz Nov 29, 2023
fcfec95
[dataset-updater] fix possible use of uninitialized `Timestamp` (#9658)
abtink Nov 29, 2023
319112b
[border-router] add missing `otBorderRoutingDhcp6PdGetState()` declar…
abtink Nov 29, 2023
af36786
[posix] unify the infra netif and backbone netif (#9638)
superwhd Nov 29, 2023
1d2b549
[cli] add Doxygen for SRP server (#9653)
jrhodie Nov 29, 2023
0da5af5
[spinel] fix multipan rcp enable config (#9659)
gytxxsy Nov 29, 2023
1f7ab82
[bbr-local] smaller enhancements (#9657)
abtink Nov 29, 2023
63424e2
[tlvs] add `ReadValueAs()` & `WriteValueAs()` to simplify `MeshCop::T…
abtink Nov 29, 2023
85660ce
[srp-client] exclude non-preferred addresses in `AutoAddress` mode (#…
abtink Nov 29, 2023
c60657a
[dataset] introduce `ContainsTlv()` method (#9663)
abtink Nov 29, 2023
41ef807
[github-actions] cover NCP build with `OT_OPERATIONAL_DATASET_AUTO_IN…
abtink Nov 29, 2023
bbc6d0b
[link-metrics] add Subject status check when getting link metrics dat…
Irving-cl Nov 30, 2023
bbb161b
[test] update `channel-announce-recovery` test to check router recove…
abtink Dec 1, 2023
5665de2
[posix] fix a few compiler warnings around nat64 (#9666)
erjiaqing Dec 1, 2023
685094b
[dataset] introduce `WriteTlv()` methods (#9664)
abtink Dec 1, 2023
a1816c1
[meshcop-tlvs] update name style in `DiscoveryResponseTlv` (#9671)
abtink Dec 1, 2023
2b57941
[cli] SRP Server Readme update - add srp server auto command (#9669)
jrhodie Dec 1, 2023
b78b71b
[srp-client] track registered addresses (#9652)
abtink Dec 1, 2023
c378bd9
[meshcop] `MeshCoP::ChannelTlv` to utilize `Mle::ChannelTlvValue` (#9…
abtink Dec 4, 2023
2d030c4
github-actions: bump actions/setup-python from 4.7.0 to 4.7.1 (#9677)
dependabot[bot] Dec 4, 2023
3d01ffa
[spinel] add vendor hook for the spinel host side (#9560)
thcu-gp Dec 4, 2023
5cab158
[tcat] initial commit of bluetooth-based commissioning (#9210)
canisLupus1313 Dec 4, 2023
93ad604
[crypto] add error handling to PBDF2 generate key function (#9655)
joerchan Nov 28, 2023
e76ddf1
[crypto] remove PSA mbedtls hybrid setup of PBKDF2 function (#9655)
joerchan Nov 28, 2023
19af711
[telemetry] add api for DHCPv6 PD telemetry (#9645)
sherysheng Dec 4, 2023
11622a1
[posix] expose `platformInfraIfIsRunning()` as `otSysInfraIfIsRunning…
superwhd Dec 5, 2023
4ed44bc
[tests] rename `get_netdata_nat64_prefix()` to `get_netdata_nat64_rou…
superwhd Dec 5, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
[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 <[email protected]>
MarekPorwisz authored Nov 29, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
commit 834c8cbc8e6b31cf63a7d6d8ca39d86eda3e1cc2
4 changes: 4 additions & 0 deletions .github/workflows/unit.yml
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions doc/ot_api_doc.h
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion etc/cmake/options.cmake
Original file line number Diff line number Diff line change
@@ -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 "- - - - - - - - - - - - - - - - ")

# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
32 changes: 29 additions & 3 deletions examples/apps/ncp/main.c
Original file line number Diff line number Diff line change
@@ -42,13 +42,24 @@
#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.
*
* @param[in] aInstance The OpenThread instance structure.
*
*/
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

13 changes: 13 additions & 0 deletions examples/apps/ncp/ncp.c
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions examples/platforms/simulation/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -68,6 +68,7 @@ add_library(openthread-simulation
infra_if.c
logging.c
misc.c
multipan.c
radio.c
spi-stubs.c
system.c
66 changes: 66 additions & 0 deletions examples/platforms/simulation/multipan.c
Original file line number Diff line number Diff line change
@@ -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 <errno.h>
#include <sys/time.h>

#include <openthread/platform/multipan.h>

#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;
}
1 change: 1 addition & 0 deletions include/openthread/BUILD.gn
Original file line number Diff line number Diff line change
@@ -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",
19 changes: 18 additions & 1 deletion include/openthread/instance.h
Original file line number Diff line number Diff line change
@@ -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.
*
10 changes: 10 additions & 0 deletions include/openthread/ncp.h
Original file line number Diff line number Diff line change
@@ -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.
*
145 changes: 145 additions & 0 deletions include/openthread/platform/multipan.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

#include <openthread/error.h>
#include <openthread/instance.h>

#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_
10 changes: 10 additions & 0 deletions src/core/api/instance_api.cpp
Original file line number Diff line number Diff line change
@@ -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;
30 changes: 30 additions & 0 deletions src/core/config/misc.h
Original file line number Diff line number Diff line change
@@ -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
*
29 changes: 29 additions & 0 deletions src/core/instance/instance.cpp
Original file line number Diff line number Diff line change
@@ -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<Instance *>(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)
{
15 changes: 15 additions & 0 deletions src/core/instance/instance.hpp
Original file line number Diff line number Diff line change
@@ -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

/**
11 changes: 11 additions & 0 deletions src/lib/spinel/openthread-spinel-config.h
Original file line number Diff line number Diff line change
@@ -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_
212 changes: 161 additions & 51 deletions src/lib/spinel/radio_spinel.cpp

Large diffs are not rendered by default.

211 changes: 192 additions & 19 deletions src/lib/spinel/radio_spinel.hpp
Original file line number Diff line number Diff line change
@@ -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,15 +354,15 @@ 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.
*
* @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.

2 changes: 2 additions & 0 deletions src/lib/spinel/spinel.c
Original file line number Diff line number Diff line change
@@ -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"},
48 changes: 38 additions & 10 deletions src/lib/spinel/spinel.h
Original file line number Diff line number Diff line change
@@ -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<uint8_t>((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
24 changes: 21 additions & 3 deletions src/lib/spinel/spinel_interface.hpp
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions src/ncp/BUILD.gn
Original file line number Diff line number Diff line change
@@ -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",
10 changes: 10 additions & 0 deletions src/ncp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
29 changes: 29 additions & 0 deletions src/ncp/example_vendor_hook.cpp
Original file line number Diff line number Diff line change
@@ -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<ot::Instance *>(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
101 changes: 101 additions & 0 deletions src/ncp/multipan_platform.cpp
Original file line number Diff line number Diff line change
@@ -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 <openthread/instance.h>
#include <openthread/platform/multipan.h>

#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<ot::Instance *>(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;
}
162 changes: 126 additions & 36 deletions src/ncp/ncp_base.cpp
Original file line number Diff line number Diff line change
@@ -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<uint8_t>(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<SPINEL_PROP_PHY_CHAN>(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<SPINEL_PROP_MAC_15_4_SADDR>(void)

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_RAW_STREAM_ENABLED>(void)
{
return mEncoder.WriteBool(mIsRawStreamEnabled);
return mEncoder.WriteBool(mIsRawStreamEnabled[mCurCommandIid]);
}

template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_RAW_STREAM_ENABLED>(void)
@@ -1541,7 +1618,7 @@ template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_RAW_STREAM_ENABLE

#endif // OPENTHREAD_RADIO || OPENTHREAD_CONFIG_LINK_RAW_ENABLE

mIsRawStreamEnabled = enabled;
mIsRawStreamEnabled[mCurCommandIid] = enabled;

exit:
return error;
@@ -1630,7 +1707,8 @@ template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SCAN_STATE>(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<SPINEL_PROP_MAC_SCAN_STATE>(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<int8_t>(scanChannel);
scanChannel = IndexOfMSB(mScanChannelMask);
mCurScanChannel[mCurCommandIid] = static_cast<int8_t>(scanChannel);

error = otLinkRawEnergyScan(mInstance, scanChannel, mScanPeriod, LinkRawEnergyScanDone);
}
@@ -1768,8 +1846,8 @@ template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_UNSOL_UPDATE_FILTER>(

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<SPINEL_PROP_NCP_VERSION>(void)

template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_INTERFACE_COUNT>(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
116 changes: 103 additions & 13 deletions src/ncp/ncp_base.hpp
Original file line number Diff line number Diff line change
@@ -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<Instance *>(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,15 +658,15 @@ class NcpBase

uint8_t mTxBuffer[kTxBufferSize];

spinel_tid_t mNextExpectedTid;
spinel_tid_t mNextExpectedTid[kSpinelInterfaceCount];

uint8_t mResponseQueueHead;
uint8_t mResponseQueueTail;
ResponseEntry mResponseQueue[kResponseQueueSize];

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
6 changes: 6 additions & 0 deletions src/ncp/ncp_base_dispatcher.cpp
Original file line number Diff line number Diff line change
@@ -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
102 changes: 80 additions & 22 deletions src/ncp/ncp_base_radio.cpp
Original file line number Diff line number Diff line change
@@ -35,6 +35,7 @@
#include <openthread/link.h>
#include <openthread/link_raw.h>
#include <openthread/ncp.h>
#include <openthread/platform/multipan.h>
#include <openthread/platform/radio.h>
#include <openthread/platform/time.h>

@@ -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<Mac::RxFrame *>(aAckFrame)->GetFramePending());
bool headerUpdated = static_cast<Mac::TxFrame *>(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<uint8_t>(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<SPINEL_PROP_RADIO_CAPS>(void)
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MAC_SRC_MATCH_ENABLED>(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<SPINEL_PROP_RCP_TIMESTAMP>(void)
@@ -250,9 +270,9 @@ template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_SRC_MATCH_ENABLED
{
otError error = OT_ERROR_NONE;

SuccessOrExit(error = mDecoder.ReadBool(mSrcMatchEnabled));
SuccessOrExit(error = mDecoder.ReadBool(mSrcMatchEnabled[mCurCommandIid]));

error = otLinkRawSrcMatchEnable(mInstance, mSrcMatchEnabled);
error = otLinkRawSrcMatchEnable(mInstance, mSrcMatchEnabled[mCurCommandIid]);

exit:
return error;
@@ -386,6 +406,25 @@ template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MAC_15_4_SADDR>(void)
return error;
}

#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE>(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<SPINEL_PROP_RCP_MAC_FRAME_COUNTER
return error;
}

#if OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE
template <> otError NcpBase::HandlePropertyGet<SPINEL_PROP_MULTIPAN_ACTIVE_INTERFACE>(void)
{
otInstance *instance;
spinel_iid_t iid;
otError error = OT_ERROR_NONE;

SuccessOrExit(error = otPlatMultipanGetActiveInstance(&instance));
iid = InstanceToIid(static_cast<Instance *>(instance));
SuccessOrExit(error = mEncoder.WriteUint8(static_cast<uint8_t>(iid)));

exit:
return error;
}
#endif /* OPENTHREAD_CONFIG_MULTIPAN_RCP_ENABLE */

#if OPENTHREAD_CONFIG_MLE_LINK_METRICS_SUBJECT_ENABLE
template <> otError NcpBase::HandlePropertySet<SPINEL_PROP_RCP_ENH_ACK_PROBING>(void)
{
45 changes: 45 additions & 0 deletions src/ncp/ncp_hdlc.cpp
Original file line number Diff line number Diff line change
@@ -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<ot::Instance *>(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,
13 changes: 13 additions & 0 deletions src/ncp/ncp_hdlc.hpp
Original file line number Diff line number Diff line change
@@ -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.
*
7 changes: 7 additions & 0 deletions src/posix/platform/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
70 changes: 67 additions & 3 deletions src/posix/platform/radio.cpp
Original file line number Diff line number Diff line change
@@ -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<spinel_iid_t>(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

1 change: 1 addition & 0 deletions src/posix/platform/radio.hpp
Original file line number Diff line number Diff line change
@@ -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)
11 changes: 10 additions & 1 deletion src/posix/platform/radio_url.cpp
Original file line number Diff line number Diff line change
@@ -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 {
5 changes: 5 additions & 0 deletions tests/fuzz/fuzzer_platform.cpp
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@
#include <openthread/platform/entropy.h>
#include <openthread/platform/logging.h>
#include <openthread/platform/misc.h>
#include <openthread/platform/multipan.h>
#include <openthread/platform/radio.h>
#include <openthread/platform/settings.h>

@@ -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);
101 changes: 95 additions & 6 deletions tests/unit/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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
766 changes: 766 additions & 0 deletions tests/unit/test_multipan_rcp_instances.cpp

Large diffs are not rendered by default.

57 changes: 56 additions & 1 deletion tests/unit/test_platform.cpp
Original file line number Diff line number Diff line change
@@ -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,18 +65,30 @@ ot::Instance *testInitInstance(void)

// Initialize OpenThread with the buffer
instance = otInstanceInit(instanceBuffer, &instanceBufferLength);
#endif
#else
instance = otInstanceInitSingle();
#endif

return static_cast<ot::Instance *>(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<ot::Instance *>(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"
6 changes: 5 additions & 1 deletion tests/unit/test_platform.h
Original file line number Diff line number Diff line change
@@ -38,6 +38,7 @@
#include <openthread/platform/entropy.h>
#include <openthread/platform/logging.h>
#include <openthread/platform/misc.h>
#include <openthread/platform/multipan.h>
#include <openthread/platform/radio.h>

#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