From 91d00c5fd13e8f843a0e753099f1ab60e7936d59 Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Thu, 7 May 2026 14:56:00 -0700 Subject: [PATCH] [cli] add multi-interpreter support (#13027) This commit introduces an opaque `otCliInterpreter` type and a set of new public C CLI APIs (e.g., `otCliInterpreterInit()`, `otCliInterpreterInputLine()`) to support multiple, dynamically allocated CLI interpreters per OpenThread instance. This architecture allows applications to instantiate and manage multiple concurrent CLI sessions. Backward compatibility is preserved by retaining the original `otCli*` APIs, which now interact with a single built-in static interpreter. The `OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE` configuration is also added. It enables support for the static interpreter and is enabled by default. It can be disabled to save RAM in deployments that solely use the multi-interpreter APIs. --- include/openthread/cli.h | 129 ++++++++++++++++++++++++++++------ include/openthread/instance.h | 2 +- src/cli/cli.cpp | 49 +++++++++++-- src/cli/cli.hpp | 51 ++++++++++++-- src/cli/cli_api.cpp | 40 ++++++++++- src/cli/cli_config.h | 15 ++++ 6 files changed, 248 insertions(+), 38 deletions(-) diff --git a/include/openthread/cli.h b/include/openthread/cli.h index 547fa808e..5000d22b8 100644 --- a/include/openthread/cli.h +++ b/include/openthread/cli.h @@ -36,6 +36,8 @@ #define OPENTHREAD_CLI_H_ #include +#include +#include #include #include @@ -47,17 +49,6 @@ extern "C" { #endif -/** - * Represents a CLI command. - */ -typedef struct otCliCommand -{ - const char *mName; ///< A pointer to the command string. - otError (*mCommand)(void *aContext, - uint8_t aArgsLength, - char *aArgs[]); ///< A function pointer to process the command. -} otCliCommand; - /** * @addtogroup api-cli * @@ -68,7 +59,12 @@ typedef struct otCliCommand */ /** - * Pointer is called to notify about Console output. + * Opaque type for a CLI interpreter. + */ +typedef struct otCliInterpreter otCliInterpreter; + +/** + * Pointer is called to notify about CLI interpreter output. * * @param[out] aContext A user context pointer. * @param[in] aFormat The format string. @@ -80,7 +76,62 @@ typedef int (*otCliOutputCallback)(void *aContext, const char *aFormat, va_list OT_TOOL_PRINTF_STYLE_FORMAT_ARG_CHECK(2, 0); /** - * Initialize the CLI module. + * Gets the size of the CLI interpreter object. + * + * @returns The size of the CLI interpreter object in bytes. + */ +size_t otCliInterpreterGetSize(void); + +/** + * Initializes a CLI interpreter. + * + * @param[in] aBuffer A pointer to a memory buffer for the CLI interpreter. + * @param[in] aSize The size of the memory buffer. + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aCallback A callback method called to process CLI output. + * @param[in] aContext A user context pointer. + * + * @returns A pointer to the initialized CLI interpreter, or `NULL` if @p aSize is too small. + */ +otCliInterpreter *otCliInterpreterInit(void *aBuffer, + size_t aSize, + otInstance *aInstance, + otCliOutputCallback aCallback, + void *aContext); + +/** + * Configures whether or not the CLI interpreter outputs the prompt string. + * + * Requires `OPENTHREAD_CONFIG_CLI_PROMPT_ENABLE`. + * + * It is enabled by default. + * + * @param[in] aInterpreter A pointer to a CLI interpreter. + * @param[in] aEnable TRUE to enable outputting the prompt, FALSE to disable. + */ +void otCliInterpreterSetPromptConfig(otCliInterpreter *aInterpreter, bool aEnable); + +/** + * Feeds input to the CLI interpreter. + * + * @param[in] aInterpreter A pointer to a CLI interpreter. + * @param[in] aLine A pointer to a null-terminated string. + */ +void otCliInterpreterInputLine(otCliInterpreter *aInterpreter, char *aLine); + +/** + * Finalizes the CLI interpreter. + * + * @param[in] aInterpreter A pointer to a CLI interpreter. + */ +void otCliInterpreterFinalize(otCliInterpreter *aInterpreter); + +//-------------------------------------------------------------------------------------------------------------------- + +/** + * Initialize the static CLI interpreter. + * + * Requires `OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE`. * * @param[in] aInstance The OpenThread instance structure. * @param[in] aCallback A callback method called to process CLI output. @@ -89,14 +140,36 @@ typedef int (*otCliOutputCallback)(void *aContext, const char *aFormat, va_list void otCliInit(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext); /** - * Is called to feed in a console input line. + * Gets the pointer to the static CLI interpreter. * - * @param[in] aBuf A pointer to a null-terminated string. + * Requires `OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE`. + * + * @returns A pointer to the static CLI interpreter. */ -void otCliInputLine(char *aBuf); +otCliInterpreter *otCliGetStaticInterpreter(void); /** - * Set a user command table. + * Feeds input to the static CLI interpreter. + * + * Requires `OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE`. + * + * @param[in] aLine A pointer to a null-terminated string. + */ +void otCliInputLine(char *aLine); + +/** + * Represents a user provided CLI command entry. + */ +typedef struct otCliCommand +{ + const char *mName; ///< The command string. + otError (*mCommand)(void *aContext, uint8_t aArgsLength, char *aArgs[]); ///< Command handler function pointer. +} otCliCommand; + +/** + * Set a user command table on the static CLI interpreter. + * + * Requires `OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE`. * * @param[in] aUserCommands A pointer to an array with user commands. * @param[in] aLength The @p aUserCommands length. @@ -108,7 +181,11 @@ void otCliInputLine(char *aBuf); otError otCliSetUserCommands(const otCliCommand *aUserCommands, uint8_t aLength, void *aContext); /** - * Write a number of bytes to the CLI console as a hex string. + * Write a number of bytes to the static CLI interpreter output as a hex string. + * + * Requires `OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE`. + * + * This is intended for use by user-provided CLI command handlers. * * @param[in] aBytes A pointer to data which should be printed. * @param[in] aLength @p aBytes length. @@ -116,7 +193,11 @@ otError otCliSetUserCommands(const otCliCommand *aUserCommands, uint8_t aLength, void otCliOutputBytes(const uint8_t *aBytes, uint8_t aLength); /** - * Write formatted string to the CLI console + * Write formatted string to the static CLI interpreter output. + * + * Requires `OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE`. + * + * This is intended for use by user-provided CLI command handlers. * * @param[in] aFmt A pointer to the format string. * @param[in] ... A matching list of arguments. @@ -124,7 +205,11 @@ void otCliOutputBytes(const uint8_t *aBytes, uint8_t aLength); void otCliOutputFormat(const char *aFmt, ...) OT_TOOL_PRINTF_STYLE_FORMAT_ARG_CHECK(1, 2); /** - * Write error code to the CLI console + * Write a given error code as the result of previous command to the static CLI interpreter output. + * + * Requires `OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE`. + * + * This is intended for use by user-provided CLI command handlers. * * If the @p aError is `OT_ERROR_PENDING` nothing will be outputted. * @@ -133,7 +218,7 @@ void otCliOutputFormat(const char *aFmt, ...) OT_TOOL_PRINTF_STYLE_FORMAT_ARG_CH void otCliAppendResult(otError aError); /** - * Callback to write the OpenThread Log to the CLI console + * Callback to write the OpenThread Log to the static CLI interpreter output. * * @param[in] aLogLevel The log level. * @param[in] aLogRegion The log region. @@ -148,6 +233,8 @@ void otCliPlatLogv(otLogLevel aLogLevel, otLogRegion aLogRegion, const char *aFo * * Available when `OPENTHREAD_CONFIG_CLI_VENDOR_COMMANDS_ENABLE` is enabled and * `OPENTHREAD_CONFIG_CLI_MAX_USER_CMD_ENTRIES` is greater than 1. + * + * Requires `OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE`. */ extern void otCliVendorSetUserCommands(void); diff --git a/include/openthread/instance.h b/include/openthread/instance.h index 0101f39e8..43fa05072 100644 --- a/include/openthread/instance.h +++ b/include/openthread/instance.h @@ -52,7 +52,7 @@ extern "C" { * * @note This number versions both OpenThread platform and user APIs. */ -#define OPENTHREAD_API_VERSION (595) +#define OPENTHREAD_API_VERSION (596) /** * @addtogroup api-instance diff --git a/src/cli/cli.cpp b/src/cli/cli.cpp index a7b5a14a0..4ae060bac 100644 --- a/src/cli/cli.cpp +++ b/src/cli/cli.cpp @@ -71,9 +71,6 @@ namespace ot { namespace Cli { -Interpreter *Interpreter::sInterpreter = nullptr; -static OT_DEFINE_ALIGNED_VAR(sInterpreterRaw, sizeof(Interpreter), uint64_t); - Interpreter::Interpreter(Instance *aInstance, otCliOutputCallback aCallback, void *aContext) : OutputImplementer(aCallback, aContext) , Utils(aInstance, *this) @@ -8504,11 +8501,49 @@ void Interpreter::HandleWakeupResult(otError aError) { OutputResult(aError); } #endif // OPENTHREAD_FTD || OPENTHREAD_MTD -void Interpreter::Initialize(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext) -{ - Instance *instance = static_cast(aInstance); +size_t Interpreter::GetSize(void) { return sizeof(Interpreter); } - Interpreter::sInterpreter = new (&sInterpreterRaw) Interpreter(instance, aCallback, aContext); +Interpreter *Interpreter::Init(void *aBuffer, + size_t aSize, + otInstance *aInstance, + otCliOutputCallback aCallback, + void *aContext) +{ + Interpreter *interpreter = nullptr; + Instance *instance = static_cast(aInstance); + + VerifyOrExit(aSize >= sizeof(Interpreter)); + interpreter = new (aBuffer) Interpreter(instance, aCallback, aContext); + +exit: + return interpreter; +} + +#if OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE + +Interpreter *Interpreter::sInterpreter = nullptr; + +static OT_DEFINE_ALIGNED_VAR(sInterpreterRaw, sizeof(Interpreter), uint64_t); + +void Interpreter::Init(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext) +{ + sInterpreter = Init(&sInterpreterRaw, sizeof(sInterpreterRaw), aInstance, aCallback, aContext); +} + +#endif + +void Interpreter::Finalize(void) +{ + mTimer.Stop(); + +#if (OPENTHREAD_FTD || OPENTHREAD_MTD) && OPENTHREAD_CONFIG_CLI_REGISTER_IP6_RECV_CALLBACK + otIp6SetReceiveCallback(GetInstancePtr(), nullptr, nullptr); +#endif +#if OPENTHREAD_CONFIG_DIAG_ENABLE + otDiagSetOutputCallback(GetInstancePtr(), nullptr, nullptr); +#endif + + this->~Interpreter(); } void Interpreter::OutputPrompt(void) diff --git a/src/cli/cli.hpp b/src/cli/cli.hpp index 68f762bb2..ded37fd92 100644 --- a/src/cli/cli.hpp +++ b/src/cli/cli.hpp @@ -86,6 +86,10 @@ #include "common/type_traits.hpp" #include "instance/instance.hpp" +typedef struct otCliInterpreter +{ +} otCliInterpreter; + namespace ot { /** @@ -103,7 +107,7 @@ extern "C" void otCliOutputFormat(const char *aFmt, ...); /** * Implements the CLI interpreter. */ -class Interpreter : public OutputImplementer, public Utils +class Interpreter : public otCliInterpreter, public OutputImplementer, public Utils { #if OPENTHREAD_FTD || OPENTHREAD_MTD friend class Ba; @@ -138,34 +142,65 @@ public: */ explicit Interpreter(Instance *aInstance, otCliOutputCallback aCallback, void *aContext); +#if OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE /** - * Returns a reference to the interpreter object. + * Returns a reference to the static CLI interpreter. * - * @returns A reference to the interpreter object. + * @returns A reference to the static CLI interpreter. */ static Interpreter &GetInterpreter(void) { OT_ASSERT(sInterpreter != nullptr); - return *sInterpreter; } /** - * Initializes the Console interpreter. + * Initializes the static CLI interpreter. * * @param[in] aInstance The OpenThread instance structure. * @param[in] aCallback A pointer to a callback method. * @param[in] aContext A pointer to a user context. */ - static void Initialize(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext); + static void Init(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext); /** - * Returns whether the interpreter is initialized. + * Returns whether the static CLI interpreter is initialized. * * @returns Whether the interpreter is initialized. */ static bool IsInitialized(void) { return sInterpreter != nullptr; } +#endif // OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE + + /** + * Gets the size of the CLI interpreter object. + * + * @returns The size of the CLI interpreter object in bytes. + */ + static size_t GetSize(void); + + /** + * Initializes a CLI interpreter. + * + * @param[in] aBuffer A pointer to a memory buffer for the CLI interpreter. + * @param[in] aSize The size of the memory buffer. + * @param[in] aInstance The OpenThread instance structure. + * @param[in] aCallback A callback method called to process CLI output. + * @param[in] aContext A user context pointer. + * + * @returns A pointer to the initialized CLI interpreter, or `nullptr` if @p aSize is too small. + */ + static Interpreter *Init(void *aBuffer, + size_t aSize, + otInstance *aInstance, + otCliOutputCallback aCallback, + void *aContext); + + /** + * Finalizes the CLI interpreter. + */ + void Finalize(void); + /** * Interprets a CLI command. * @@ -197,7 +232,9 @@ public: #endif protected: +#if OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE static Interpreter *sInterpreter; +#endif private: static constexpr uint8_t kIndentSize = 4; diff --git a/src/cli/cli_api.cpp b/src/cli/cli_api.cpp index 82f5bc6ef..3075654cf 100644 --- a/src/cli/cli_api.cpp +++ b/src/cli/cli_api.cpp @@ -38,16 +38,50 @@ namespace ot { namespace Cli { +extern "C" size_t otCliInterpreterGetSize(void) { return Interpreter::GetSize(); } + +extern "C" otCliInterpreter *otCliInterpreterInit(void *aBuffer, + size_t aSize, + otInstance *aInstance, + otCliOutputCallback aCallback, + void *aContext) +{ + return Interpreter::Init(aBuffer, aSize, aInstance, aCallback, aContext); +} + +#if OPENTHREAD_CONFIG_CLI_PROMPT_ENABLE +extern "C" void otCliInterpreterSetPromptConfig(otCliInterpreter *aInterpreter, bool aEnable) +{ + static_cast(aInterpreter)->SetPromptConfig(aEnable); +} +#endif + +extern "C" void otCliInterpreterInputLine(otCliInterpreter *aInterpreter, char *aLine) +{ + static_cast(aInterpreter)->ProcessLine(aLine); +} + +extern "C" void otCliInterpreterFinalize(otCliInterpreter *aInterpreter) +{ + static_cast(aInterpreter)->Finalize(); +} + +//--------------------------------------------------------------------------------------------------------------------- + +#if OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE + extern "C" void otCliInit(otInstance *aInstance, otCliOutputCallback aCallback, void *aContext) { - Interpreter::Initialize(aInstance, aCallback, aContext); + Interpreter::Init(aInstance, aCallback, aContext); #if OPENTHREAD_CONFIG_CLI_VENDOR_COMMANDS_ENABLE && OPENTHREAD_CONFIG_CLI_MAX_USER_CMD_ENTRIES > 1 otCliVendorSetUserCommands(); #endif } -extern "C" void otCliInputLine(char *aBuf) { Interpreter::GetInterpreter().ProcessLine(aBuf); } +extern "C" otCliInterpreter *otCliGetStaticInterpreter(void) { return &Interpreter::GetInterpreter(); } + +extern "C" void otCliInputLine(char *aLine) { Interpreter::GetInterpreter().ProcessLine(aLine); } extern "C" otError otCliSetUserCommands(const otCliCommand *aUserCommands, uint8_t aLength, void *aContext) { @@ -88,5 +122,7 @@ exit: return; } +#endif // OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE + } // namespace Cli } // namespace ot diff --git a/src/cli/cli_config.h b/src/cli/cli_config.h index a02b68cf9..11b667de8 100644 --- a/src/cli/cli_config.h +++ b/src/cli/cli_config.h @@ -47,6 +47,21 @@ #endif #endif +/** + * @def OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE + * + * Define as 1 to enable the static CLI interpreter. + * + * This configuration option enables the static CLI interpreter, allowing the CLI module to statically allocate and + * provide a single interpreter instance. + * + * This is intended to provide backward compatibility with the original `otCli*` APIs. It can be disabled to save RAM + * if the static CLI interpreter is not needed. + */ +#ifndef OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE +#define OPENTHREAD_CONFIG_CLI_STATIC_INTERPRETER_ENABLE 1 +#endif + /** * @def OPENTHREAD_CONFIG_CLI_MAX_LINE_LENGTH *