diff --git a/src/ncp/CMakeLists.txt b/src/ncp/CMakeLists.txt index 10b163b75b8..53041e2b228 100644 --- a/src/ncp/CMakeLists.txt +++ b/src/ncp/CMakeLists.txt @@ -29,6 +29,8 @@ add_library(otbr-ncp ncp_host.cpp ncp_host.hpp + ncp_spinel.cpp + ncp_spinel.hpp rcp_host.cpp rcp_host.hpp thread_host.cpp diff --git a/src/ncp/ncp_host.cpp b/src/ncp/ncp_host.cpp index 7deb64fe039..0d9cfa643eb 100644 --- a/src/ncp/ncp_host.cpp +++ b/src/ncp/ncp_host.cpp @@ -57,17 +57,18 @@ const char *NcpHost::GetCoprocessorVersion(void) void NcpHost::Init(void) { otSysInit(&mConfig); + mNcpSpinel.Init(&mSpinelDriver); } void NcpHost::Deinit(void) { + mNcpSpinel.Deinit(); otSysDeinit(); } void NcpHost::GetDeviceRole(DeviceRoleHandler aHandler) { - // TODO: Implement the API with NCP Spinel - aHandler(OT_ERROR_NOT_IMPLEMENTED, OT_DEVICE_ROLE_DISABLED); + mNcpSpinel.GetDeviceRole(aHandler); } void NcpHost::Process(const MainloopContext &aMainloop) diff --git a/src/ncp/ncp_host.hpp b/src/ncp/ncp_host.hpp index 0e44570fb7d..31f5df74c1e 100644 --- a/src/ncp/ncp_host.hpp +++ b/src/ncp/ncp_host.hpp @@ -38,6 +38,7 @@ #include "lib/spinel/spinel_driver.hpp" #include "common/mainloop.hpp" +#include "ncp/ncp_spinel.hpp" #include "ncp/thread_host.hpp" namespace otbr { @@ -76,6 +77,7 @@ class NcpHost : public MainloopProcessor, public ThreadHost private: ot::Spinel::SpinelDriver &mSpinelDriver; otPlatformConfig mConfig; + NcpSpinel mNcpSpinel; }; } // namespace Ncp diff --git a/src/ncp/ncp_spinel.cpp b/src/ncp/ncp_spinel.cpp new file mode 100644 index 00000000000..105e1913735 --- /dev/null +++ b/src/ncp/ncp_spinel.cpp @@ -0,0 +1,273 @@ +/* + * Copyright (c) 2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define OTBR_LOG_TAG "NcpSpinel" + +#include "ncp_spinel.hpp" + +#include + +#include + +#include "common/code_utils.hpp" +#include "common/logging.hpp" +#include "lib/spinel/spinel.h" +#include "lib/spinel/spinel_driver.hpp" + +namespace otbr { +namespace Ncp { + +NcpSpinel::NcpSpinel(void) + : mSpinelDriver(nullptr) + , mCmdTidsInUse(0) + , mCmdNextTid(1) + , mDeviceRole(OT_DEVICE_ROLE_DISABLED) + , mGetDeviceRoleHandler(nullptr) +{ + memset(mWaitingKeyTable, 0xff, sizeof(mWaitingKeyTable)); +} + +NcpSpinel::~NcpSpinel(void) = default; + +void NcpSpinel::Init(ot::Spinel::SpinelDriver *aSpinelDriver) +{ + mSpinelDriver = aSpinelDriver; + mSpinelDriver->SetFrameHandler(&HandleReceivedFrame, &HandleSavedFrame, this); +} + +void NcpSpinel::Deinit(void) +{ + mSpinelDriver = nullptr; +} + +void NcpSpinel::GetDeviceRole(GetDeviceRoleHandler aHandler) +{ + otError error = OT_ERROR_NONE; + spinel_tid_t tid = GetNextTid(); + va_list args; + + error = mSpinelDriver->SendCommand(SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_NET_ROLE, tid, nullptr, args); + if (error != OT_ERROR_NONE) + { + FreeTid(tid); + aHandler(error, OT_DEVICE_ROLE_DISABLED); + } + mWaitingKeyTable[tid] = SPINEL_PROP_NET_ROLE; + + mGetDeviceRoleHandler = aHandler; + + return; +} + +void NcpSpinel::HandleReceivedFrame(const uint8_t *aFrame, + uint16_t aLength, + uint8_t aHeader, + bool &aSave, + void *aContext) +{ + static_cast(aContext)->HandleReceivedFrame(aFrame, aLength, aHeader, aSave); +} + +void NcpSpinel::HandleReceivedFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aShouldSaveFrame) +{ + spinel_tid_t tid = SPINEL_HEADER_GET_TID(aHeader); + + if (tid == 0) + { + HandleNotification(aFrame, aLength); + } + else if (tid < kMaxTids) + { + HandleResponse(tid, aFrame, aLength); + } + else + { + otbrLogCrit("Received unexpected tid: %u", tid); + } + + aShouldSaveFrame = false; +} + +void NcpSpinel::HandleSavedFrame(const uint8_t *aFrame, uint16_t aLength, void *aContext) +{ + OT_UNUSED_VARIABLE(aFrame); + OT_UNUSED_VARIABLE(aLength); + OT_UNUSED_VARIABLE(aContext); +} + +void NcpSpinel::HandleNotification(const uint8_t *aFrame, uint16_t aLength) +{ + spinel_prop_key_t key; + spinel_size_t len = 0; + spinel_ssize_t unpacked; + uint8_t *data = nullptr; + uint32_t cmd; + uint8_t header; + otbrError error = OTBR_ERROR_NONE; + + unpacked = spinel_datatype_unpack(aFrame, aLength, "CiiD", &header, &cmd, &key, &data, &len); + VerifyOrExit(unpacked > 0, error = OTBR_ERROR_PARSE); + VerifyOrExit(SPINEL_HEADER_GET_TID(header) == 0, error = OTBR_ERROR_PARSE); + VerifyOrExit(cmd == SPINEL_CMD_PROP_VALUE_IS); + HandleValueIs(key, data, static_cast(len)); + +exit: + if (error != OTBR_ERROR_NONE) + { + otbrLogResult(error, "HandleNotification: %s", __FUNCTION__); + } +} + +void NcpSpinel::HandleResponse(spinel_tid_t aTid, const uint8_t *aFrame, uint16_t aLength) +{ + spinel_prop_key_t key; + spinel_size_t len = 0; + spinel_ssize_t unpacked; + uint8_t *data = nullptr; + uint32_t cmd; + uint8_t header; + otError error = OT_ERROR_NONE; + + unpacked = spinel_datatype_unpack(aFrame, aLength, "CiiD", &header, &cmd, &key, &data, &len); + VerifyOrExit(unpacked > 0, error = OT_ERROR_PARSE); + + VerifyOrExit(cmd == SPINEL_CMD_PROP_VALUE_IS && key == mWaitingKeyTable[aTid], error = OT_ERROR_INVALID_STATE); + + if (key == SPINEL_PROP_NET_ROLE) + { + spinel_net_role_t spinelRole; + otDeviceRole role; + unpacked = spinel_datatype_unpack(data, len, "i", &spinelRole); + + switch (spinelRole) + { + case SPINEL_NET_ROLE_DISABLED: + role = OT_DEVICE_ROLE_DISABLED; + break; + case SPINEL_NET_ROLE_DETACHED: + role = OT_DEVICE_ROLE_DETACHED; + break; + case SPINEL_NET_ROLE_CHILD: + role = OT_DEVICE_ROLE_CHILD; + break; + case SPINEL_NET_ROLE_ROUTER: + role = OT_DEVICE_ROLE_ROUTER; + break; + case SPINEL_NET_ROLE_LEADER: + role = OT_DEVICE_ROLE_LEADER; + break; + } + + if (mGetDeviceRoleHandler) + { + mGetDeviceRoleHandler(error, role); + } + } + +exit: + if (error == OT_ERROR_INVALID_STATE) + { + otbrLogCrit("Received unexpected response with cmd:%u, key:%u, waiting key:%u for tid:%u", cmd, key, + mWaitingKeyTable[aTid], aTid); + } +} + +void NcpSpinel::HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength) +{ + otbrError error = OTBR_ERROR_NONE; + spinel_ssize_t unpacked; + + if (aKey == SPINEL_PROP_LAST_STATUS) + { + spinel_status_t status = SPINEL_STATUS_OK; + + unpacked = spinel_datatype_unpack(aBuffer, aLength, "i", &status); + VerifyOrExit(unpacked > 0, error = OTBR_ERROR_PARSE); + + otbrLogInfo("NCP last status: %s", spinel_status_to_cstr(status)); + } + else if (aKey == SPINEL_PROP_NET_ROLE) + { + spinel_net_role_t role; + + unpacked = spinel_datatype_unpack(aBuffer, aLength, "C", &role); + VerifyOrExit(unpacked > 0, error = OTBR_ERROR_PARSE); + + switch (role) + { + case SPINEL_NET_ROLE_DETACHED: + mDeviceRole = OT_DEVICE_ROLE_DETACHED; + break; + case SPINEL_NET_ROLE_CHILD: + mDeviceRole = OT_DEVICE_ROLE_CHILD; + break; + case SPINEL_NET_ROLE_ROUTER: + mDeviceRole = OT_DEVICE_ROLE_ROUTER; + break; + case SPINEL_NET_ROLE_LEADER: + mDeviceRole = OT_DEVICE_ROLE_LEADER; + break; + case SPINEL_NET_ROLE_DISABLED: + mDeviceRole = OT_DEVICE_ROLE_DISABLED; + break; + } + + otbrLogInfo("Device role changed to %s", otThreadDeviceRoleToString(mDeviceRole)); + } + +exit: + otbrLogResult(error, "NcpSpinel: %s", __FUNCTION__); + return; +} + +spinel_tid_t NcpSpinel::GetNextTid(void) +{ + spinel_tid_t tid = mCmdNextTid; + + while (((1 << tid) & mCmdTidsInUse) != 0) + { + tid = SPINEL_GET_NEXT_TID(tid); + + if (tid == mCmdNextTid) + { + // We looped back to `mCmdNextTid` indicating that all + // TIDs are in-use. + + ExitNow(tid = 0); + } + } + + mCmdTidsInUse |= (1 << tid); + mCmdNextTid = SPINEL_GET_NEXT_TID(tid); + +exit: + return tid; +} + +} // namespace Ncp +} // namespace otbr diff --git a/src/ncp/ncp_spinel.hpp b/src/ncp/ncp_spinel.hpp new file mode 100644 index 00000000000..3576ff60eb6 --- /dev/null +++ b/src/ncp/ncp_spinel.hpp @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2024, The OpenThread Authors. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the copyright holder nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file + * This file includes definitions for the spinel based Thread controller. + */ + +#ifndef OTBR_AGENT_NCP_SPINEL_HPP_ +#define OTBR_AGENT_NCP_SPINEL_HPP_ + +#include + +#include +#include +#include + +#include "lib/spinel/spinel.h" +#include "lib/spinel/spinel_driver.hpp" + +namespace otbr { +namespace Ncp { + +/** + * The class for controlling the Thread stack on the network NCP co-processor (NCP). + * + */ +class NcpSpinel +{ +public: + /** + * Constructor. + * + */ + NcpSpinel(void); + + /** + * Destructor. + * + */ + ~NcpSpinel(void); + + /** + * Do the initialization. + * + * @param[in] aSoftwareReset TRUE to try SW reset first, FALSE to directly try HW reset. + * @param[in] aSpinelDriver Pointer to the SpinelDriver instance that this object depends. + * + */ + void Init(ot::Spinel::SpinelDriver *aSpinelDriver); + + /** + * Do the de-initialization. + * + */ + void Deinit(void); + + /** + * Returns the Co-processor version string. + * + */ + const char *GetCoprocessorVersion(void) { return mSpinelDriver->GetVersion(); } + + /** + * This method gets the device role and return the role through the handler. + * + * @param[in] aHandler A handler to return the role. + * + */ + using GetDeviceRoleHandler = std::function; + void GetDeviceRole(GetDeviceRoleHandler aHandler); + +private: + static constexpr uint8_t kMaxTids = 16; + + static void HandleReceivedFrame(const uint8_t *aFrame, + uint16_t aLength, + uint8_t aHeader, + bool &aSave, + void *aContext); + void HandleReceivedFrame(const uint8_t *aFrame, uint16_t aLength, uint8_t aHeader, bool &aShouldSaveFrame); + static void HandleSavedFrame(const uint8_t *aFrame, uint16_t aLength, void *aContext); + + void HandleNotification(const uint8_t *aFrame, uint16_t aLength); + void HandleResponse(spinel_tid_t aTid, const uint8_t *aFrame, uint16_t aLength); + void HandleValueIs(spinel_prop_key_t aKey, const uint8_t *aBuffer, uint16_t aLength); + + spinel_tid_t GetNextTid(void); + void FreeTid(spinel_tid_t tid) { mCmdTidsInUse &= ~(1 << tid); } + + ot::Spinel::SpinelDriver *mSpinelDriver; + uint16_t mCmdTidsInUse; ///< Used transaction ids. + spinel_tid_t mCmdNextTid; ///< Next available transaction id. + spinel_prop_key_t mWaitingKeyTable[kMaxTids]; ///< The property key of current transaction. + + otDeviceRole mDeviceRole; + GetDeviceRoleHandler mGetDeviceRoleHandler; +}; + +} // namespace Ncp +} // namespace otbr + +#endif // OTBR_AGENT_NCP_SPINEL_HPP_ diff --git a/src/ncp/rcp_host.cpp b/src/ncp/rcp_host.cpp index fcab4ceb1a9..cda44a435c5 100644 --- a/src/ncp/rcp_host.cpp +++ b/src/ncp/rcp_host.cpp @@ -26,7 +26,7 @@ * POSSIBILITY OF SUCH DAMAGE. */ -#define OTBR_LOG_TAG "NCP" +#define OTBR_LOG_TAG "RCP_HOST" #include "ncp/rcp_host.hpp" diff --git a/tests/scripts/expect/ncp_get_device_role.exp b/tests/scripts/expect/ncp_get_device_role.exp index c1cf5a99284..20a51eabf0d 100755 --- a/tests/scripts/expect/ncp_get_device_role.exp +++ b/tests/scripts/expect/ncp_get_device_role.exp @@ -35,7 +35,7 @@ sleep 1 spawn dbus-send --system --dest=io.openthread.BorderRouter.wpan0 --print-reply --reply-timeout=1000 /io/openthread/BorderRouter/wpan0 org.freedesktop.DBus.Properties.Get string:io.openthread.BorderRouter string:DeviceRole -expect -re {NotImplemented} { +expect -re {disabled} { } timeout { puts "timeout!" exit 1