diff --git a/src/posix/platform/CMakeLists.txt b/src/posix/platform/CMakeLists.txt index d38bebbfb864..b4ac241f53eb 100644 --- a/src/posix/platform/CMakeLists.txt +++ b/src/posix/platform/CMakeLists.txt @@ -143,6 +143,7 @@ add_library(openthread-posix power.cpp radio.cpp radio_url.cpp + rcp_caps_diag.cpp resolver.cpp settings.cpp spinel_manager.cpp diff --git a/src/posix/platform/README_RCP_CAPS_DIAG.md b/src/posix/platform/README_RCP_CAPS_DIAG.md new file mode 100644 index 000000000000..fb3d6ed0ea7a --- /dev/null +++ b/src/posix/platform/README_RCP_CAPS_DIAG.md @@ -0,0 +1,33 @@ +# OpenThread Diagnostics - RCP Capability Diagnostics Example + +This module provides diag commands for checking RCP capabilities. + +`OPENTHREAD_CONFIG_DIAG_ENABLE` and `OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE` are required. + +## Command List + +- [spinel](#spinel) + +## Command Details + +### spinel + +Check which Spinel commands RCP supports. + +```bash +> diag rcpcaps spinel + +Basic : +PROP_VALUE_GET CAPS --------------------------------------- OK + +Thread Version >= 1.1 : +PROP_VALUE_SET PHY_CHAN ----------------------------------- OK + +Thread Version >= 1.2 : +PROP_VALUE_SET ENH_ACK_PROBING ---------------------------- NotImplemented + +Optional : +PROP_VALUE_GET PHY_CCA_THRESHOLD -------------------------- OK +Done +``` + diff --git a/src/posix/platform/openthread-posix-config.h b/src/posix/platform/openthread-posix-config.h index ade8ab2c6043..e789d74ae90a 100644 --- a/src/posix/platform/openthread-posix-config.h +++ b/src/posix/platform/openthread-posix-config.h @@ -429,4 +429,13 @@ #define OPENTHREAD_POSIX_CONFIG_TREL_TX_PACKET_POOL_SIZE 5 #endif +/** + * @def OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE + * + * Define as 1 to enable RCP capability diagnostic support. + * + */ +#ifndef OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE +#define OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE 1 +#endif #endif // OPENTHREAD_PLATFORM_POSIX_CONFIG_H_ diff --git a/src/posix/platform/radio.cpp b/src/posix/platform/radio.cpp index b8ce7497d3e2..eabeaf6bb79a 100644 --- a/src/posix/platform/radio.cpp +++ b/src/posix/platform/radio.cpp @@ -64,6 +64,9 @@ const char Radio::kLogModuleName[] = "Radio"; Radio::Radio(void) : mRadioUrl(nullptr) , mRadioSpinel() +#if OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE + , mRcpCapsDiag(mRadioSpinel) +#endif { } @@ -194,6 +197,10 @@ void Radio::ProcessMaxPowerTable(const RadioUrl &aRadioUrl) ot::Spinel::RadioSpinel &GetRadioSpinel(void) { return sRadio.GetRadioSpinel(); } +#if OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE +ot::Posix::RcpCapsDiag &GetRcpCapsDiag(void) { return sRadio.GetRcpCapsDiag(); } +#endif + void platformRadioDeinit(void) { GetRadioSpinel().Deinit(); } void otPlatRadioGetIeeeEui64(otInstance *aInstance, uint8_t *aIeeeEui64) @@ -503,6 +510,13 @@ otError otPlatDiagProcess(otInstance *aInstance, char *cur = cmd; char *end = cmd + sizeof(cmd); +#if OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE + if (strcmp(aArgs[0], "rcpcaps") == 0) + { + return GetRcpCapsDiag().DiagProcess(aArgs, aArgsLength, aOutput, aOutputMaxLen); + } +#endif + for (uint8_t index = 0; (index < aArgsLength) && (cur < end); index++) { cur += snprintf(cur, static_cast(end - cur), "%s ", aArgs[index]); diff --git a/src/posix/platform/radio.hpp b/src/posix/platform/radio.hpp index 4caa85f8de5c..4a5bea4084b8 100644 --- a/src/posix/platform/radio.hpp +++ b/src/posix/platform/radio.hpp @@ -32,6 +32,7 @@ #include "hdlc_interface.hpp" #include "logger.hpp" #include "radio_url.hpp" +#include "rcp_caps_diag.hpp" #include "spi_interface.hpp" #include "spinel_manager.hpp" #include "vendor_interface.hpp" @@ -86,6 +87,16 @@ class Radio : public Logger */ Spinel::RadioSpinel &GetRadioSpinel(void) { return mRadioSpinel; } + /** + * Acts as an accessor to the RCP capability diagnostic instance used by the radio. + * + * @returns A reference to the RCP capability diagnostic instance. + * + */ +#if OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE + RcpCapsDiag &GetRcpCapsDiag(void) { return mRcpCapsDiag; } +#endif + private: void ProcessRadioUrl(const RadioUrl &aRadioUrl); void ProcessMaxPowerTable(const RadioUrl &aRadioUrl); @@ -110,6 +121,10 @@ class Radio : public Logger #else Spinel::RadioSpinel mRadioSpinel; #endif + +#if OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE + RcpCapsDiag mRcpCapsDiag; +#endif }; } // namespace Posix diff --git a/src/posix/platform/rcp_caps_diag.cpp b/src/posix/platform/rcp_caps_diag.cpp new file mode 100644 index 000000000000..b814724ff55a --- /dev/null +++ b/src/posix/platform/rcp_caps_diag.cpp @@ -0,0 +1,190 @@ +/* + * 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. + */ + +#include "rcp_caps_diag.hpp" + +#if OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE +namespace ot { +namespace Posix { +#define SPINEL_ENTRY(aCategory, aCommand, aKey) \ + { \ + aCategory, aCommand, aKey, &RcpCapsDiag::HandleSpinelCommand \ + } + +template <> otError RcpCapsDiag::HandleSpinelCommand(void) +{ + int8_t ccaThreshold; + + return mRadioSpinel.GetCcaEnergyDetectThreshold(ccaThreshold); +} + +template <> otError RcpCapsDiag::HandleSpinelCommand(void) +{ + static constexpr uint8_t kPhyChannel = 22; + + return mRadioSpinel.Set(SPINEL_PROP_PHY_CHAN, SPINEL_DATATYPE_UINT8_S, kPhyChannel); +} + +template <> otError RcpCapsDiag::HandleSpinelCommand(void) +{ + static constexpr uint8_t kCapsBufferSize = 100; + uint8_t capsBuffer[kCapsBufferSize]; + spinel_size_t capsLength = sizeof(capsBuffer); + + return mRadioSpinel.Get(SPINEL_PROP_CAPS, SPINEL_DATATYPE_DATA_S, capsBuffer, &capsLength); +} + +template <> otError RcpCapsDiag::HandleSpinelCommand(void) +{ + uint16_t shortAddress = 0x1122; + otExtAddress extAddress = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}; + uint8_t flags = SPINEL_THREAD_LINK_METRIC_PDU_COUNT | SPINEL_THREAD_LINK_METRIC_LQI | + SPINEL_THREAD_LINK_METRIC_LINK_MARGIN | SPINEL_THREAD_LINK_METRIC_RSSI; + + return mRadioSpinel.Set(SPINEL_PROP_RCP_ENH_ACK_PROBING, + SPINEL_DATATYPE_UINT16_S SPINEL_DATATYPE_EUI64_S SPINEL_DATATYPE_UINT8_S, shortAddress, + extAddress.m8, flags); +} + +const struct RcpCapsDiag::SpinelEntry RcpCapsDiag::sSpinelEntries[] = { + // Basic Spinel commands + SPINEL_ENTRY(kCategoryBasic, SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_CAPS), + + // Thread Version >= 1.1 + SPINEL_ENTRY(kCategoryThread1_1, SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_PHY_CHAN), + + // Thread Version >= 1.2 + SPINEL_ENTRY(kCategoryThread1_2, SPINEL_CMD_PROP_VALUE_SET, SPINEL_PROP_RCP_ENH_ACK_PROBING), + + // Optional Spinel commands + SPINEL_ENTRY(kCategoryOptional, SPINEL_CMD_PROP_VALUE_GET, SPINEL_PROP_PHY_CCA_THRESHOLD), +}; + +otError RcpCapsDiag::DiagProcess(char *aArgs[], uint8_t aArgsLength, char *aOutput, size_t aOutputMaxLen) +{ + otError error = OT_ERROR_NONE; + + VerifyOrExit(aArgsLength == 2, error = OT_ERROR_INVALID_ARGS); + + mOutputStart = aOutput; + mOutputEnd = aOutput + aOutputMaxLen; + + if (strcmp(aArgs[1], "spinel") == 0) + { + ProcessSpinel(); + } + else + { + error = OT_ERROR_INVALID_COMMAND; + } + + mOutputStart = nullptr; + mOutputEnd = nullptr; + +exit: + return error; +} + +void RcpCapsDiag::ProcessSpinel(void) +{ + for (uint8_t i = 0; i < kNumCategories; i++) + { + TesSpinelCommands(static_cast(i)); + } +} + +void RcpCapsDiag::TesSpinelCommands(Category aCategory) +{ + otError error; + + Output("\r\n%s :\r\n", CategoryToString(aCategory)); + + for (const SpinelEntry &property : sSpinelEntries) + { + if (property.mCategory != aCategory) + { + continue; + } + + error = (this->*property.mHandler)(); + OutputResult(property, error); + } +} + +void RcpCapsDiag::OutputResult(const SpinelEntry &aEntry, otError error) +{ + static constexpr uint8_t kSpaceLength = 1; + static constexpr uint8_t kMaxCommandStringLength = 20; + static constexpr uint8_t kMaxKeyStringLength = 35; + static constexpr uint16_t kMaxLength = kMaxCommandStringLength + kMaxKeyStringLength + kSpaceLength; + static const char kPadding[] = "----------------------------------------------------------"; + const char *commandString = spinel_command_to_cstr(aEntry.mCommand); + const char *keyString = spinel_prop_key_to_cstr(aEntry.mKey); + uint16_t actualLength = strlen(commandString) + strlen(keyString) + kSpaceLength; + uint16_t paddingOffset = (actualLength > kMaxLength) ? kMaxLength : actualLength; + + static_assert(kMaxLength < sizeof(kPadding), "Padding bytes are too short"); + + Output("%.*s %.*s %s %s\r\n", kMaxCommandStringLength, commandString, kMaxKeyStringLength, keyString, + &kPadding[paddingOffset], otThreadErrorToString(error)); +} + +void RcpCapsDiag::Output(const char *aFormat, ...) +{ + va_list args; + + va_start(args, aFormat); + + if ((mOutputStart != nullptr) && (mOutputEnd != nullptr) && (mOutputStart < mOutputEnd)) + { + mOutputStart += vsnprintf(mOutputStart, static_cast(mOutputEnd - mOutputStart), aFormat, args); + } + + va_end(args); +} + +const char *RcpCapsDiag::CategoryToString(Category aCategory) +{ + static const char *const kCategoryStrings[] = { + "Basic", // (0) kCategoryBasic + "Thread Version >= 1.1", // (1) kCategoryThread1_1 + "Thread Version >= 1.2", // (2) kCategoryThread1_2 + "Optional", // (3) kCategoryOptional + }; + + static_assert(kCategoryBasic == 0, "kCategoryBasic value is incorrect"); + static_assert(kCategoryThread1_1 == 1, "kCategoryThread1_1 value is incorrect"); + static_assert(kCategoryThread1_2 == 2, "kCategoryThread1_2 value is incorrect"); + static_assert(kCategoryOptional == 3, "kCategoryOptional value is incorrect"); + + return (aCategory < OT_ARRAY_LENGTH(kCategoryStrings)) ? kCategoryStrings[aCategory] : "invalid"; +} + +} // namespace Posix +} // namespace ot +#endif // OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE diff --git a/src/posix/platform/rcp_caps_diag.hpp b/src/posix/platform/rcp_caps_diag.hpp new file mode 100644 index 000000000000..fbf8128de576 --- /dev/null +++ b/src/posix/platform/rcp_caps_diag.hpp @@ -0,0 +1,119 @@ +/* + * 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 contains definitions for the RCP capability diagnostics module. + */ + +#ifndef OT_POSIX_PLATFORM_RCP_CAPS_DIAG_HPP_ +#define OT_POSIX_PLATFORM_RCP_CAPS_DIAG_HPP_ + +#include "platform-posix.h" + +#if OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE +#include "lib/spinel/radio_spinel.hpp" +#include "lib/spinel/spinel.h" + +#if !OPENTHREAD_CONFIG_DIAG_ENABLE +#error "OPENTHREAD_CONFIG_DIAG_ENABLE is required for OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE" +#endif + +namespace ot { +namespace Posix { + +class RcpCapsDiag +{ +public: + /** + * Constructor initializes the object. + * + * @param[in] aRadioSpinel A reference to the Spinel::RadioSpinel instance. + * + */ + explicit RcpCapsDiag(Spinel::RadioSpinel &aRadioSpinel) + : mRadioSpinel(aRadioSpinel) + , mOutputStart(nullptr) + , mOutputEnd(nullptr) + { + } + + /** + * Processes RCP capability diagnostics commands. + * + * @param[in] aArgs The arguments of diagnostics command line. + * @param[in] aArgsLength The number of arguments in @p aArgs. + * @param[out] aOutput The diagnostics execution result. + * @param[in] aOutputMaxLen The output buffer size. + * + * @retval OT_ERROR_INVALID_ARGS The command is supported but invalid arguments provided. + * @retval OT_ERROR_NONE The command is successfully process. + * @retval OT_ERROR_INVALID_COMMAND The command is not valid or not supported. + * + */ + otError DiagProcess(char *aArgs[], uint8_t aArgsLength, char *aOutput, size_t aOutputMaxLen); + +private: + template otError HandleSpinelCommand(void); + typedef otError (RcpCapsDiag::*SpinelCommandHandler)(void); + + enum Category : uint8_t + { + kCategoryBasic, + kCategoryThread1_1, + kCategoryThread1_2, + kCategoryOptional, + kNumCategories, + }; + + struct SpinelEntry + { + Category mCategory; + uint32_t mCommand; + spinel_prop_key_t mKey; + RcpCapsDiag::SpinelCommandHandler mHandler; + }; + + void ProcessSpinel(void); + void TesSpinelCommands(Category aCategory); + void OutputResult(const SpinelEntry &aEntry, otError error); + void Output(const char *aFormat, ...); + + const char *CategoryToString(Category aCategory); + + static const struct SpinelEntry sSpinelEntries[]; + + Spinel::RadioSpinel &mRadioSpinel; + char *mOutputStart; + char *mOutputEnd; +}; + +} // namespace Posix +} // namespace ot +#endif // OPENTHREAD_POSIX_CONFIG_RCP_CAPS_DIAG_ENABLE +#endif // OT_POSIX_PLATFORM_RCP_CAPS_DIAG_HPP_