diff --git a/drivers/modem/modem_shell.c b/drivers/modem/modem_shell.c index 505dd9ab8c91..6baf3e48e58e 100644 --- a/drivers/modem/modem_shell.c +++ b/drivers/modem/modem_shell.c @@ -14,7 +14,7 @@ #include #include #include -#include +#include #include diff --git a/ext/Kconfig b/ext/Kconfig index effb2d9ff580..e8bf7ea3d091 100644 --- a/ext/Kconfig +++ b/ext/Kconfig @@ -14,6 +14,8 @@ source "ext/lib/crypto/Kconfig" source "ext/lib/encoding/Kconfig" +source "ext/lib/fnmatch/Kconfig" + source "ext/lib/ipc/open-amp/Kconfig" source "ext/lib/mgmt/Kconfig" diff --git a/ext/lib/CMakeLists.txt b/ext/lib/CMakeLists.txt index a347f2d37f0f..4e0dc1dfd43f 100644 --- a/ext/lib/CMakeLists.txt +++ b/ext/lib/CMakeLists.txt @@ -2,3 +2,4 @@ add_subdirectory(crypto) add_subdirectory(encoding) add_subdirectory(ipc) add_subdirectory(mgmt) +add_subdirectory_ifdef(CONFIG_FNMATCH fnmatch) diff --git a/ext/lib/fnmatch/CMakeLists.txt b/ext/lib/fnmatch/CMakeLists.txt new file mode 100644 index 000000000000..480031f79130 --- /dev/null +++ b/ext/lib/fnmatch/CMakeLists.txt @@ -0,0 +1,15 @@ +# Copyright (c) 2018 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 +# + +zephyr_include_directories_ifdef( + CONFIG_FNMATCH + . +) + +zephyr_sources_ifdef( + CONFIG_FNMATCH + fnmatch.c +) + diff --git a/ext/lib/fnmatch/Kconfig b/ext/lib/fnmatch/Kconfig new file mode 100644 index 000000000000..1681778a6fc9 --- /dev/null +++ b/ext/lib/fnmatch/Kconfig @@ -0,0 +1,12 @@ +# +# Copyright (c) 2018 Nordic Semiconductor +# +# SPDX-License-Identifier: Apache-2.0 +# + +config FNMATCH + bool + prompt "Fnmatch Support" + help + This option enables the fnmatch library + diff --git a/ext/lib/fnmatch/fnmatch.c b/ext/lib/fnmatch/fnmatch.c new file mode 100644 index 000000000000..f84feadead0f --- /dev/null +++ b/ext/lib/fnmatch/fnmatch.c @@ -0,0 +1,208 @@ +/* $NetBSD: fnmatch.c,v 1.26 2014/10/12 22:32:33 christos Exp $ */ + +/* + * Copyright (c) 1989, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Guido van Rossum. + * + * 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 University 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 REGENTS 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 REGENTS 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. + */ + +/* + * Function fnmatch() as specified in POSIX 1003.2-1992, section B.6. + * Compares a filename or pathname to a pattern. + */ + +#include +#include "fnmatch.h" +#include + +#define EOS '\0' + +static inline int foldcase(int ch, int flags) +{ + + if ((flags & FNM_CASEFOLD) != 0 && isupper(ch)) + return tolower(ch); + return ch; +} + +#define FOLDCASE(ch, flags) foldcase((unsigned char)(ch), (flags)) + +static const char * rangematch(const char *pattern, int test, int flags) +{ + int negate, ok, need; + char c, c2; + + if (pattern == NULL) + { + return NULL; + } + + /* + * A bracket expression starting with an unquoted circumflex + * character produces unspecified results (IEEE 1003.2-1992, + * 3.13.2). This implementation treats it like '!', for + * consistency with the regular expression syntax. + * J.T. Conklin (conklin@ngai.kaleida.com) + */ + if ((negate = (*pattern == '!' || *pattern == '^')) != 0) + ++pattern; + + need = 1; + for (ok = 0; (c = FOLDCASE(*pattern++, flags)) != ']' || need;) { + need = 0; + if (c == '/') + return (void *)-1; + if (c == '\\' && !(flags & FNM_NOESCAPE)) + c = FOLDCASE(*pattern++, flags); + if (c == EOS) + return NULL; + if (*pattern == '-' + && (c2 = FOLDCASE(*(pattern + 1), flags)) != EOS && + c2 != ']') { + pattern += 2; + if (c2 == '\\' && !(flags & FNM_NOESCAPE)) + c2 = FOLDCASE(*pattern++, flags); + if (c2 == EOS) + return NULL; + if (c <= test && test <= c2) + ok = 1; + } else if (c == test) + ok = 1; + } + return ok == negate ? NULL : pattern; +} + + +static int fnmatchx(const char *pattern, const char *string, int flags, size_t recursion) +{ + const char *stringstart, *r; + char c, test; + + if ((pattern == NULL) || (string == NULL)) + { + return FNM_NOMATCH; + } + + if (recursion-- == 0) + return FNM_NORES; + + for (stringstart = string;;) { + switch (c = FOLDCASE(*pattern++, flags)) { + case EOS: + if ((flags & FNM_LEADING_DIR) && *string == '/') + return 0; + return *string == EOS ? 0 : FNM_NOMATCH; + case '?': + if (*string == EOS) + return FNM_NOMATCH; + if (*string == '/' && (flags & FNM_PATHNAME)) + return FNM_NOMATCH; + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return FNM_NOMATCH; + ++string; + break; + case '*': + c = FOLDCASE(*pattern, flags); + /* Collapse multiple stars. */ + while (c == '*') + c = FOLDCASE(*++pattern, flags); + + if (*string == '.' && (flags & FNM_PERIOD) && + (string == stringstart || + ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) + return FNM_NOMATCH; + + /* Optimize for pattern with * at end or before /. */ + if (c == EOS) { + if (flags & FNM_PATHNAME) + return (flags & FNM_LEADING_DIR) || + strchr(string, '/') == NULL ? + 0 : FNM_NOMATCH; + else + return 0; + } else if (c == '/' && flags & FNM_PATHNAME) { + if ((string = strchr(string, '/')) == NULL) + return FNM_NOMATCH; + break; + } + + /* General case, use recursion. */ + while ((test = FOLDCASE(*string, flags)) != EOS) { + int e; + switch ((e = fnmatchx(pattern, string, + flags & ~FNM_PERIOD, recursion))) { + case FNM_NOMATCH: + break; + default: + return e; + } + if (test == '/' && flags & FNM_PATHNAME) + break; + ++string; + } + return FNM_NOMATCH; + case '[': + if (*string == EOS) + return FNM_NOMATCH; + if (*string == '/' && flags & FNM_PATHNAME) + return FNM_NOMATCH; + if ((r = rangematch(pattern, + FOLDCASE(*string, flags), flags)) == NULL) + return FNM_NOMATCH; + if (r == (void *)-1) { + if (*string != '[') + return FNM_NOMATCH; + } else + pattern = r; + ++string; + break; + case '\\': + if (!(flags & FNM_NOESCAPE)) { + if ((c = FOLDCASE(*pattern++, flags)) == EOS) { + c = '\0'; + --pattern; + } + } + /* FALLTHROUGH */ + default: + if (c != FOLDCASE(*string++, flags)) + return FNM_NOMATCH; + break; + } + } + /* NOTREACHED */ +} + +int fnmatch(const char *pattern, const char *string, int flags) +{ + return fnmatchx(pattern, string, flags, 64); +} + diff --git a/ext/lib/fnmatch/fnmatch.h b/ext/lib/fnmatch/fnmatch.h new file mode 100644 index 000000000000..2696dd54ffca --- /dev/null +++ b/ext/lib/fnmatch/fnmatch.h @@ -0,0 +1,50 @@ +/* $NetBSD: fnmatch.h,v 1.12.50.1 2011/02/08 16:18:55 bouyer Exp $ */ + +/*- + * Copyright (c) 1992, 1993 + * The Regents of the University of California. 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 University 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 REGENTS 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 REGENTS 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. + * + * @(#)fnmatch.h 8.1 (Berkeley) 6/2/93 + */ + +#ifndef _FNMATCH_H_ +#define _FNMATCH_H_ + +#define FNM_NOMATCH 1 /* Match failed. */ +#define FNM_NOSYS 2 /* Function not implemented. */ +#define FNM_NORES 3 /* Out of resources */ + +#define FNM_NOESCAPE 0x01 /* Disable backslash escaping. */ +#define FNM_PATHNAME 0x02 /* Slash must be matched by slash. */ +#define FNM_PERIOD 0x04 /* Period must be matched by period. */ +#define FNM_CASEFOLD 0x08 /* Pattern is matched case-insensitive */ +#define FNM_LEADING_DIR 0x10 /* Ignore / after Imatch. */ + +int fnmatch(const char *, const char *, int); + +#endif /* !_FNMATCH_H_ */ + diff --git a/include/linker/common-rom.ld b/include/linker/common-rom.ld index 59bdd0ea54c4..502d82533b5a 100644 --- a/include/linker/common-rom.ld +++ b/include/linker/common-rom.ld @@ -73,3 +73,10 @@ KEEP(*(".log_backends")); __log_backends_end = .; } GROUP_LINK_IN(ROMABLE_REGION) + + SECTION_DATA_PROLOGUE(shell_root_cmds_sections, (OPTIONAL),) + { + __shell_root_cmds_start = .; + KEEP(*(SORT(.shell_root_cmd_*))); + __shell_root_cmds_end = .; + } GROUP_LINK_IN(ROMABLE_REGION) diff --git a/include/logging/log_ctrl.h b/include/logging/log_ctrl.h index 685d2f9499c9..698ecf311f56 100644 --- a/include/logging/log_ctrl.h +++ b/include/logging/log_ctrl.h @@ -104,7 +104,7 @@ u32_t log_src_cnt_get(u32_t domain_id); * @param domain_id Domain ID. * @param src_id Source ID. * - * @return Source name. + * @return Source name or NULL if invalid arguments. */ const char *log_source_name_get(u32_t domain_id, u32_t src_id); diff --git a/include/misc/dlist.h b/include/misc/dlist.h index cdb4366bc2f7..fa5de63af9b8 100644 --- a/include/misc/dlist.h +++ b/include/misc/dlist.h @@ -310,6 +310,41 @@ static inline sys_dnode_t *sys_dlist_peek_next(sys_dlist_t *list, return node ? sys_dlist_peek_next_no_check(list, node) : NULL; } +/** + * @brief get a reference to the previous item in the list, node is not NULL + * + * Faster than sys_dlist_peek_prev() if node is known not to be NULL. + * + * @param list the doubly-linked list to operate on + * @param node the node from which to get the previous element in the list + * + * @return a pointer to the previous element from a node, NULL if node is the + * tail + */ + +static inline sys_dnode_t *sys_dlist_peek_prev_no_check(sys_dlist_t *list, + sys_dnode_t *node) +{ + return (node == list->head) ? NULL : node->prev; +} + +/** + * @brief get a reference to the previous item in the list + * + * @param list the doubly-linked list to operate on + * @param node the node from which to get the previous element in the list + * + * @return a pointer to the previous element from a node, NULL if node is the + * tail or NULL (when node comes from reading the head of an empty + * list). + */ + +static inline sys_dnode_t *sys_dlist_peek_prev(sys_dlist_t *list, + sys_dnode_t *node) +{ + return node ? sys_dlist_peek_prev_no_check(list, node) : NULL; +} + /** * @brief get a reference to the tail item in the list * diff --git a/include/shell/legacy_shell.h b/include/shell/legacy_shell.h new file mode 100644 index 000000000000..4b67038ce7ae --- /dev/null +++ b/include/shell/legacy_shell.h @@ -0,0 +1,228 @@ +/* shell.h - Shell header */ + +/* + * Copyright (c) 2015 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_SHELL_SHELL_H_ +#define ZEPHYR_INCLUDE_SHELL_SHELL_H_ + +#ifdef __cplusplus +extern "C" { +#endif + + +struct device; + +/** @brief Callback called when command is entered. + * + * @param argc Number of parameters passed. + * @param argv Array of option strings. First option is always command name. + * + * @return 0 in case of success or negative value in case of error. + */ +typedef int (*shell_cmd_function_t)(int argc, char *argv[]); + +struct shell_cmd { + const char *cmd_name; + shell_cmd_function_t cb; + const char *help; + const char *desc; +}; + +/** @brief Callback to get the current prompt. + * + * @returns Current prompt string. + */ +typedef const char *(*shell_prompt_function_t)(void); + +/** @brief Callback for custom line2argv parsing + * + * If this callback is set, command parsing for specified module works only + * if it is selected as default module. This is to not break other modules. + * + * @returns Current prompt string. + */ +typedef size_t (*shell_line2argv_function_t)(char *str, char *argv[], + size_t size); + +struct shell_module { + const char *module_name; + const struct shell_cmd *commands; + shell_prompt_function_t prompt; + shell_line2argv_function_t line2argv; +}; + +/** @typedef shell_mcumgr_function_t + * @brief Callback that is executed when an mcumgr packet is received over the + * shell. + * + * The packet argument must be exactly what was received over the console, + * except the terminating newline must be replaced with '\0'. + * + * @param line The received mcumgr packet. + * @param arg An optional argument. + * + * @return on success; negative error code on failure. + */ +typedef int (*shell_mcumgr_function_t)(const char *line, void *arg); + +/** + * @brief Kernel Shell API + * @defgroup _shell_api_functions Shell API Functions + * @{ + */ + +/** + * @def SHELL_REGISTER + * + * @brief Create shell_module object and set it up for boot time initialization. + * + * @details This macro defines a shell_module object that is automatically + * configured by the kernel during system initialization. + * + * @param _name Module name to be entered in shell console. + * @param _commands Array of commands to register. + * Shell array entries must be packed to calculate array size correctly. + */ + +/** + * @def SHELL_REGISTER_WITH_PROMPT + * + * @brief Create shell_module object and set it up for boot time initialization. + * + * @details This macro defines a shell_module object that is automatically + * configured by the kernel during system initialization, in addition to that + * this also enables setting a custom prompt handler when the module is + * selected. + * + * @param _name Module name to be entered in shell console. + * @param _commands Array of commands to register. + * Shell array entries must be packed to calculate array size correctly. + * @param _prompt Optional prompt handler to be set when module is selected. + */ + + +/** + * @def SHELL_REGISTER_COMMAND + * + * @brief Create a standalone command and set it up for boot time + * initialization. + * + * @details This macro defines a shell_cmd object that is automatically + * configured by the kernel during system initialization. + * + * The command will be available in the default module, so it will be available + * immediately. + * + */ +#ifdef CONFIG_CONSOLE_SHELL +#define SHELL_REGISTER(_name, _commands) \ + SHELL_REGISTER_WITH_PROMPT_AND_LINE2ARGV(_name, _commands, NULL, NULL) + +#define SHELL_REGISTER_WITH_PROMPT(_name, _commands, _prompt) \ + SHELL_REGISTER_WITH_PROMPT_AND_LINE2ARGV(_name, _commands, _prompt, NULL) + +#define SHELL_REGISTER_WITH_LINE2ARGV(_name, _commands, _line2argv) \ + SHELL_REGISTER_WITH_PROMPT_AND_LINE2ARGV(_name, _commands, NULL, \ + _line2argv) + +#define SHELL_REGISTER_WITH_PROMPT_AND_LINE2ARGV(_name, _commands, _prompt, \ + _line2argv) \ + \ + static struct shell_module (__shell__name) __used \ + __attribute__((__section__(".shell_module_"))) = { \ + .module_name = _name, \ + .commands = _commands, \ + .prompt = _prompt, \ + .line2argv = _line2argv, \ + } + +#define SHELL_REGISTER_COMMAND(name, callback, _help) \ + \ + const struct shell_cmd (__shell__##name) __used \ + __attribute__((__section__(".shell_cmd_"))) = { \ + .cmd_name = name, \ + .cb = callback, \ + .help = _help \ + } +#else +#define SHELL_REGISTER(_name, _commands) +#define SHELL_REGISTER_WITH_PROMPT(_name, _commands, _prompt) +#define SHELL_REGISTER_COMMAND(_name, _callback, _help) +#endif + +/** @brief Initialize shell with optional prompt, NULL in case no prompt is + * needed. + * + * @param prompt Prompt to be printed on serial console. + */ +void shell_init(const char *prompt); + +/** @brief Optionally register an app default cmd handler. + * + * @param handler To be called if no cmd found in cmds registered with + * shell_init. + */ +void shell_register_app_cmd_handler(shell_cmd_function_t handler); + +/** @brief Optionally register a custom prompt callback. + * + * @param handler To be called to get the current prompt. + */ +void shell_register_prompt_handler(shell_prompt_function_t handler); + +/** @brief Optionally register a default module, to eliminate typing it in + * shell console or for backwards compatibility. + * + * @param name Module name. + */ +void shell_register_default_module(const char *name); + +/** @brief Configures a callback for received mcumgr packets. + * + * @param handler The callback to execute when an mcumgr packet is received. + * @param arg An optional argument to pass to the callback. + */ +void shell_register_mcumgr_handler(shell_mcumgr_function_t handler, void *arg); + +/** @brief Execute command line. + * + * Pass command line to shell to execute. The line cannot be a C string literal + * since it will be modified in place, instead a variable can be used: + * + * char cmd[] = "command"; + * shell_exec(cmd); + * + * Note: This by no means makes any of the commands a stable interface, so + * this function should only be used for debugging/diagnostic. + * + * @param line Command line to be executed + * @returns Result of the execution + */ +int shell_exec(char *line); + +/** + * @} + */ + + +#ifdef CONFIG_CONSOLE_SHELL +int shell_run(struct device *dev); +#else +static inline int shell_run(struct device *dev) +{ + ARG_UNUSED(dev); + + return 0; +} +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_SHELL_SHELL_H_ */ diff --git a/include/shell/shell.h b/include/shell/shell.h index cb4ff879ecf2..8c00e2cf56a1 100644 --- a/include/shell/shell.h +++ b/include/shell/shell.h @@ -1,228 +1,566 @@ -/* shell.h - Shell header */ - /* - * Copyright (c) 2015 Intel Corporation + * Copyright (c) 2018 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ -#ifndef ZEPHYR_INCLUDE_SHELL_SHELL_H_ -#define ZEPHYR_INCLUDE_SHELL_SHELL_H_ +#ifndef SHELL_H__ +#define SHELL_H__ + +#include +#include +#include +#include +#include +#include +#include +#include #ifdef __cplusplus extern "C" { #endif +#define SHELL_RX_BUFF_SIZE 16 -struct device; +#ifndef CONFIG_SHELL_CMD_BUFF_SIZE +#define CONFIG_SHELL_CMD_BUFF_SIZE 0 +#endif + +#ifndef CONFIG_SHELL_PRINTF_BUFF_SIZE +#define CONFIG_SHELL_PRINTF_BUFF_SIZE 0 +#endif -/** @brief Callback called when command is entered. +#define SHELL_CMD_ROOT_LVL (0u) + +/* + * @defgroup shell Shell + * @ingroup subsys * - * @param argc Number of parameters passed. - * @param argv Array of option strings. First option is always command name. + * @brief Module for providing shell. * - * @return 0 in case of success or negative value in case of error. + * @{ */ -typedef int (*shell_cmd_function_t)(int argc, char *argv[]); -struct shell_cmd { - const char *cmd_name; - shell_cmd_function_t cb; - const char *help; - const char *desc; -}; +struct shell_static_entry; -/** @brief Callback to get the current prompt. +/* + * @brief Shell dynamic command descriptor. * - * @returns Current prompt string. + * @details Function shall fill the received shell_static_entry structure + * with requested (idx) dynamic subcommand data. If there is more than + * one dynamic subcommand available, the function shall ensure that the + * returned commands: entry->syntax are sorted in alphabetical order. + * If idx exceeds the available dynamic subcommands, the function must + * write to entry->syntax NULL value. This will indicate to the shell + * module that there are no more dynamic commands to read. */ -typedef const char *(*shell_prompt_function_t)(void); +typedef void (*shell_dynamic_get)(size_t idx, + struct shell_static_entry *entry); -/** @brief Callback for custom line2argv parsing - * - * If this callback is set, command parsing for specified module works only - * if it is selected as default module. This is to not break other modules. - * - * @returns Current prompt string. +/* + * @brief Shell command descriptor. */ -typedef size_t (*shell_line2argv_function_t)(char *str, char *argv[], - size_t size); - -struct shell_module { - const char *module_name; - const struct shell_cmd *commands; - shell_prompt_function_t prompt; - shell_line2argv_function_t line2argv; +struct shell_cmd_entry { + bool is_dynamic; + union { + /*!< Pointer to function returning dynamic commands.*/ + shell_dynamic_get dynamic_get; + + /*!< Pointer to array of static commands. */ + const struct shell_static_entry *entry; + } u; }; -/** @typedef shell_mcumgr_function_t - * @brief Callback that is executed when an mcumgr packet is received over the - * shell. - * - * The packet argument must be exactly what was received over the console, - * except the terminating newline must be replaced with '\0'. - * - * @param line The received mcumgr packet. - * @param arg An optional argument. - * - * @return on success; negative error code on failure. +struct shell; + +/* + * @brief Shell command handler prototype. */ -typedef int (*shell_mcumgr_function_t)(const char *line, void *arg); +typedef void (*shell_cmd_handler)(const struct shell *shell, + size_t argc, char **argv); -/** - * @brief Kernel Shell API - * @defgroup _shell_api_functions Shell API Functions - * @{ +/* + * @brief Shell static command descriptor. */ +struct shell_static_entry { + const char *syntax; /*!< Command syntax strings. */ + const char *help; /*!< Command help string. */ + const struct shell_cmd_entry *subcmd; /*!< Pointer to subcommand. */ + shell_cmd_handler handler; /*!< Command handler. */ +}; -/** - * @def SHELL_REGISTER +#define SHELL_CMD_NAME(name) UTIL_CAT(shell_cmd_, name) +/* + * @brief Macro for defining and adding a root command (level 0). * - * @brief Create shell_module object and set it up for boot time initialization. + * @note Each root command shall have unique syntax. * - * @details This macro defines a shell_module object that is automatically - * configured by the kernel during system initialization. + * @param[in] syntax Command syntax (for example: history). + * @param[in] subcmd Pointer to a subcommands array. + * @param[in] help Pointer to a command help string. + * @param[in] handler Pointer to a function handler. + */ +#define SHELL_CMD_REGISTER(syntax, subcmd, help, handler) \ + static const struct shell_static_entry UTIL_CAT(shell_, syntax) = \ + SHELL_CMD(syntax, subcmd, help, handler); \ + static const struct shell_cmd_entry UTIL_CAT(shell_cmd_, syntax) \ + __attribute__ ((section("." \ + STRINGIFY(UTIL_CAT(shell_root_cmd_, syntax))))) \ + __attribute__((used)) = { \ + .is_dynamic = false, \ + .u.entry = &UTIL_CAT(shell_, syntax) \ + } + +/* + * @brief Macro for creating a subcommand set. It must be used outside of any + * function body. * - * @param _name Module name to be entered in shell console. - * @param _commands Array of commands to register. - * Shell array entries must be packed to calculate array size correctly. + * @param[in] name Name of the subcommand set. */ +#define SHELL_CREATE_STATIC_SUBCMD_SET(name) \ + static const struct shell_static_entry shell_##name[]; \ + static const struct shell_cmd_entry name = { \ + .is_dynamic = false, \ + .u.entry = shell_##name \ + }; \ + static const struct shell_static_entry shell_##name[] = -/** - * @def SHELL_REGISTER_WITH_PROMPT +/* + * @brief Define ending subcommands set. * - * @brief Create shell_module object and set it up for boot time initialization. + */ +#define SHELL_SUBCMD_SET_END {NULL} + +/* + * @brief Macro for creating a dynamic entry. * - * @details This macro defines a shell_module object that is automatically - * configured by the kernel during system initialization, in addition to that - * this also enables setting a custom prompt handler when the module is - * selected. + * @param[in] name Name of the dynamic entry. + * @param[in] get Pointer to the function returning dynamic commands array + */ +#define SHELL_CREATE_DYNAMIC_CMD(name, get) \ + static const struct shell_cmd_entry name = { \ + .is_dynamic = true, \ + .u.dynamic_get = get \ + } + +/* + * @brief Initializes a shell command. * - * @param _name Module name to be entered in shell console. - * @param _commands Array of commands to register. - * Shell array entries must be packed to calculate array size correctly. - * @param _prompt Optional prompt handler to be set when module is selected. + * @param[in] _syntax Command syntax (for example: history). + * @param[in] _subcmd Pointer to a subcommands array. + * @param[in] _help Pointer to a command help string. + * @param[in] _handler Pointer to a function handler. */ +#define SHELL_CMD(_syntax, _subcmd, _help, _handler) { \ + .syntax = (const char *)STRINGIFY(_syntax), \ + .subcmd = _subcmd, \ + .help = (const char *)_help, \ + .handler = _handler \ +} +/* + * @internal @brief Internal shell state in response to data received from the + * terminal. + */ +enum shell_receive_state { + SHELL_RECEIVE_DEFAULT, + SHELL_RECEIVE_ESC, + SHELL_RECEIVE_ESC_SEQ, + SHELL_RECEIVE_TILDE_EXP +}; -/** - * @def SHELL_REGISTER_COMMAND - * - * @brief Create a standalone command and set it up for boot time - * initialization. - * - * @details This macro defines a shell_cmd object that is automatically - * configured by the kernel during system initialization. - * - * The command will be available in the default module, so it will be available - * immediately. - * + +/* + * @internal @brief Internal shell state. */ -#ifdef CONFIG_CONSOLE_SHELL -#define SHELL_REGISTER(_name, _commands) \ - SHELL_REGISTER_WITH_PROMPT_AND_LINE2ARGV(_name, _commands, NULL, NULL) +enum shell_state { + SHELL_STATE_UNINITIALIZED, + SHELL_STATE_INITIALIZED, + SHELL_STATE_ACTIVE, + SHELL_STATE_PANIC_MODE_ACTIVE, /*!< Panic activated.*/ + SHELL_STATE_PANIC_MODE_INACTIVE /*!< Panic requested, not supported.*/ +}; -#define SHELL_REGISTER_WITH_PROMPT(_name, _commands, _prompt) \ - SHELL_REGISTER_WITH_PROMPT_AND_LINE2ARGV(_name, _commands, _prompt, NULL) +/* @brief Shell transport event. */ +enum shell_transport_evt { + SHELL_TRANSPORT_EVT_RX_RDY, + SHELL_TRANSPORT_EVT_TX_RDY +}; -#define SHELL_REGISTER_WITH_LINE2ARGV(_name, _commands, _line2argv) \ - SHELL_REGISTER_WITH_PROMPT_AND_LINE2ARGV(_name, _commands, NULL, \ - _line2argv) +typedef void (*shell_transport_handler_t)(enum shell_transport_evt evt, + void *context); -#define SHELL_REGISTER_WITH_PROMPT_AND_LINE2ARGV(_name, _commands, _prompt, \ - _line2argv) \ - \ - static struct shell_module (__shell__name) __used \ - __attribute__((__section__(".shell_module_"))) = { \ - .module_name = _name, \ - .commands = _commands, \ - .prompt = _prompt, \ - .line2argv = _line2argv, \ - } +struct shell_transport; -#define SHELL_REGISTER_COMMAND(name, callback, _help) \ - \ - const struct shell_cmd (__shell__##name) __used \ - __attribute__((__section__(".shell_cmd_"))) = { \ - .cmd_name = name, \ - .cb = callback, \ - .help = _help \ - } +/* + * @brief Unified shell transport interface. + */ +struct shell_transport_api { + /* + * @brief Function for initializing the shell transport interface. + * + * @param[in] transport Pointer to the transfer instance. + * @param[in] config Pointer to instance configuration. + * @param[in] evt_handler Event handler. + * @param[in] context Pointer to the context passed to event + * handler. + * + * @return Standard error code. + */ + int (*init)(const struct shell_transport *transport, + const void *config, + shell_transport_handler_t evt_handler, + void *context); + + /* + * @brief Function for uninitializing the shell transport interface. + * + * @param[in] transport Pointer to the transfer instance. + * + * @return Standard error code. + */ + int (*uninit)(const struct shell_transport *transport); + + /* + * @brief Function for reconfiguring the transport to work in blocking + * mode. + * + * @param transport Pointer to the transfer instance. + * @param blocking If true, the transport is enabled in blocking mode. + * + * @return NRF_SUCCESS on successful enabling, error otherwise (also if + * not supported). + */ + int (*enable)(const struct shell_transport *transport, bool blocking); + + /* + * @brief Function for writing data to the transport interface. + * + * @param[in] transport Pointer to the transfer instance. + * @param[in] data Pointer to the source buffer. + * @param[in] length Source buffer length. + * @param[in] cnt Pointer to the sent bytes counter. + * + * @return Standard error code. + */ + int (*write)(const struct shell_transport *transport, + const void *data, size_t length, size_t *cnt); + + /* + * @brief Function for reading data from the transport interface. + * + * @param[in] p_transport Pointer to the transfer instance. + * @param[in] p_data Pointer to the destination buffer. + * @param[in] length Destination buffer length. + * @param[in] cnt Pointer to the received bytes counter. + * + * @return Standard error code. + */ + int (*read)(const struct shell_transport *transport, + void *data, size_t length, size_t *cnt); + +}; + +struct shell_transport { + const struct shell_transport_api *api; + void *ctx; +}; + +/** @brief Shell statistics structure. */ +struct shell_stats { + u32_t log_lost_cnt; /*!< Lost log counter.*/ +}; + +#if CONFIG_SHELL_STATS +#define SHELL_STATS_DEFINE(_name) static struct shell_stats _name##_stats +#define SHELL_STATS_PTR(_name) (&(_name##_stats)) #else -#define SHELL_REGISTER(_name, _commands) -#define SHELL_REGISTER_WITH_PROMPT(_name, _commands, _prompt) -#define SHELL_REGISTER_COMMAND(_name, _callback, _help) -#endif +#define SHELL_STATS_DEFINE(_name) +#define SHELL_STATS_PTR(_name) NULL +#endif /* CONFIG_SHELL_STATS */ -/** @brief Initialize shell with optional prompt, NULL in case no prompt is - * needed. - * - * @param prompt Prompt to be printed on serial console. +/* + * @internal @brief Flags for internal shell usage. + */ +struct shell_flags { + u32_t insert_mode :1; /*!< Controls insert mode for text introduction.*/ + u32_t show_help :1; /*!< Shows help if -h or --help option present.*/ + u32_t use_colors :1; /*!< Controls colored syntax.*/ + u32_t echo :1; /*!< Controls shell echo.*/ + u32_t processing :1; /*!< Shell is executing process function.*/ + u32_t tx_rdy :1; + u32_t mode_delete :1; /*!< Operation mode of backspace key */ +}; + +_Static_assert(sizeof(struct shell_flags) == sizeof(u32_t), "Must fit in 32b."); + +/* + * @internal @brief Union for internal shell usage. + */ +union shell_internal { + u32_t value; + struct shell_flags flags; +}; + +enum shell_signal { + SHELL_SIGNAL_RXRDY, + SHELL_SIGNAL_TXDONE, + SHELL_SIGNAL_LOG_MSG, + SHELL_SIGNAL_KILL, + SHELL_SIGNALS +}; + +/* + * @brief Shell instance context. + */ +struct shell_ctx { + enum shell_state state; /*!< Internal module state.*/ + enum shell_receive_state receive_state;/*!< Escape sequence indicator.*/ + + /*!< Currently executed command.*/ + struct shell_static_entry active_cmd; + + /*!< VT100 color and cursor position, terminal width.*/ + struct shell_vt100_ctx vt100_ctx; + + u16_t cmd_buff_len;/*!< Command length.*/ + u16_t cmd_buff_pos; /*!< Command buffer cursor position.*/ + + u16_t cmd_tmp_buff_len; /*!< Command length in tmp buffer.*/ + + /*!< Command input buffer.*/ + char cmd_buff[CONFIG_SHELL_CMD_BUFF_SIZE]; + + /*!< Command temporary buffer.*/ + char temp_buff[CONFIG_SHELL_CMD_BUFF_SIZE]; + + /*!< Printf buffer size.*/ + char printf_buff[CONFIG_SHELL_PRINTF_BUFF_SIZE]; + + volatile union shell_internal internal; /*!< Internal shell data.*/ + + struct k_poll_signal signals[SHELL_SIGNALS]; + struct k_poll_event events[SHELL_SIGNALS]; +}; + +extern const struct log_backend_api log_backend_shell_api; + +/* + * @brief Shell instance internals. */ -void shell_init(const char *prompt); +struct shell { + const char *const name; /*!< Terminal name. */ + + const struct shell_transport *iface; /*!< Transport interface.*/ + struct shell_ctx *ctx; /*!< Internal context.*/ + + struct shell_history *history; + + const struct shell_fprintf *fprintf_ctx; -/** @brief Optionally register an app default cmd handler. + struct shell_stats *stats; + + const struct shell_log_backend *log_backend; + + LOG_INSTANCE_PTR_DECLARE(log); + + /*!< New line character, only allowed values: \\n and \\r.*/ + const char newline_char; + + struct k_thread *thread; + k_thread_stack_t *stack; +}; + +/* + * @brief Macro for defining a shell instance. * - * @param handler To be called if no cmd found in cmds registered with - * shell_init. + * @param[in] _name Instance name. + * @param[in] shell_prefix Shell prefix string. + * @param[in] transport_iface Pointer to the transport interface. + * @param[in] newline_ch New line character - only allowed values are + * '\\n' or '\\r'. + * @param[in] log_queue_size Logger processing queue size. */ -void shell_register_app_cmd_handler(shell_cmd_function_t handler); +#define SHELL_DEFINE(_name, shell_prefix, transport_iface, \ + newline_ch, log_queue_size) \ + static const struct shell _name; \ + static struct shell_ctx UTIL_CAT(_name, _ctx); \ + static u8_t _name##_out_buffer[CONFIG_SHELL_PRINTF_BUFF_SIZE]; \ + SHELL_LOG_BACKEND_DEFINE(_name, _name##_out_buffer, \ + CONFIG_SHELL_PRINTF_BUFF_SIZE); \ + SHELL_HISTORY_DEFINE(_name, 128, 8);/*todo*/ \ + SHELL_FPRINTF_DEFINE(_name## _fprintf, &_name, _name##_out_buffer, \ + CONFIG_SHELL_PRINTF_BUFF_SIZE, \ + true, shell_print_stream); \ + LOG_INSTANCE_REGISTER(shell, _name, CONFIG_SHELL_LOG_LEVEL); \ + SHELL_STATS_DEFINE(_name); \ + static K_THREAD_STACK_DEFINE(_name##_stack, CONFIG_SHELL_STACK_SIZE);\ + static struct k_thread _name##_thread; \ + static const struct shell _name = { \ + .name = shell_prefix, \ + .iface = transport_iface, \ + .ctx = &UTIL_CAT(_name, _ctx), \ + .history = SHELL_HISTORY_PTR(_name), \ + .fprintf_ctx = &_name##_fprintf, \ + .stats = SHELL_STATS_PTR(_name), \ + .log_backend = SHELL_LOG_BACKEND_PTR(_name), \ + LOG_INSTANCE_PTR_INIT(log, shell, _name) \ + .newline_char = newline_ch, \ + .thread = &_name##_thread, \ + .stack = _name##_stack \ + } -/** @brief Optionally register a custom prompt callback. +/* + * @brief Function for initializing a transport layer and internal shell state. * - * @param handler To be called to get the current prompt. + * @param[in] shell Pointer to shell instance. + * @param[in] transport_config Transport configuration during initialization. + * @param[in] use_colors Enables colored prints. + * @param[in] log_backend If true, the console will be used as logger + * backend. + * @param[in] init_log_level Default severity level for the logger. + * + * @return Standard error code. */ -void shell_register_prompt_handler(shell_prompt_function_t handler); +int shell_init(const struct shell *shell, const void *transport_config, + bool use_colors, bool log_backend, u32_t init_log_level); -/** @brief Optionally register a default module, to eliminate typing it in - * shell console or for backwards compatibility. +/* + * @brief Uninitializes the transport layer and the internal shell state. + * + * @param shell Pointer to shell instance. * - * @param name Module name. + * @return Standard error code. */ -void shell_register_default_module(const char *name); +int shell_uninit(const struct shell *shell); -/** @brief Configures a callback for received mcumgr packets. +/* + * @brief Function for starting shell processing. + * + * @param shell Pointer to the shell instance. * - * @param handler The callback to execute when an mcumgr packet is received. - * @param arg An optional argument to pass to the callback. + * @return Standard error code. */ -void shell_register_mcumgr_handler(shell_mcumgr_function_t handler, void *arg); +int shell_start(const struct shell *shell); -/** @brief Execute command line. +/* + * @brief Function for stopping shell processing. * - * Pass command line to shell to execute. The line cannot be a C string literal - * since it will be modified in place, instead a variable can be used: + * @param shell Pointer to shell instance. * - * char cmd[] = "command"; - * shell_exec(cmd); + * @return Standard error code. + */ +int shell_stop(const struct shell *shell); + +/* + * @brief Shell colors for nrf_shell_fprintf function. + */ +#define SHELL_NORMAL SHELL_VT100_COLOR_DEFAULT +#define SHELL_INFO SHELL_VT100_COLOR_GREEN +#define SHELL_OPTION SHELL_VT100_COLOR_CYAN +#define SHELL_WARNING SHELL_VT100_COLOR_YELLOW +#define SHELL_ERROR SHELL_VT100_COLOR_RED + +/* + * @brief Printf-like function which sends formatted data stream to the shell. + * This function shall not be used outside of the shell command context. * - * Note: This by no means makes any of the commands a stable interface, so - * this function should only be used for debugging/diagnostic. + * @param[in] shell Pointer to the shell instance. + * @param[in] color Printf color. + * @param[in] p_fmt Format string. + * @param[in] ... List of parameters to print. + */ +void shell_fprintf(const struct shell *shell, enum shell_vt100_color color, + const char *p_fmt, ...); + +/* + * @brief Process function, which should be executed when data is ready in the + * transport interface. To be used if shell thread is disabled. * - * @param line Command line to be executed - * @returns Result of the execution + * @param[in] shell Pointer to the shell instance. */ -int shell_exec(char *line); +void shell_process(const struct shell *shell); -/** -* @} -*/ +/* + * @brief Option descriptor. + */ +struct shell_getopt_option { + const char *optname; /*!< Option long name.*/ + const char *optname_short; /*!< Option short name.*/ + const char *optname_help; /*!< Option help string.*/ +}; +/* + * @brief Option structure initializer. + * + * @param[in] _optname Option name long. + * @param[in] _shortname Option name short. + * @param[in] _help Option help string. + */ +#define SHELL_OPT(_optname, _shortname, _help) { \ + .optname = _optname, \ + .optname_short = _shortname, \ + .optname_help = _help, \ + } -#ifdef CONFIG_CONSOLE_SHELL -int shell_run(struct device *dev); -#else -static inline int shell_run(struct device *dev) +/* + * @brief Informs that a command has been called with -h or --help option. + * + * @param[in] shell Pointer to the shell instance. + * + * @return True if help has been requested. + */ +static inline bool shell_help_requested(const struct shell *shell) { - ARG_UNUSED(dev); - - return 0; + return shell->ctx->internal.flags.show_help; } -#endif +/* + * @brief Prints the current command help. + * + * Function will print a help string with: the currently entered command, its + * options,and subcommands (if they exist). + * + * @param[in] shell Pointer to the shell instance. + * @param[in] opt Pointer to the optional option array. + * @param[in] opt_len Option array size. + */ +void shell_help_print(const struct shell *shell, + const struct shell_getopt_option *opt, size_t opt_len); + +/* + * @brief Prints help if request and prints error message on wrong argument + * count. + * + * Optionally, printing help on wrong argument count can be enabled. + * + * @param[in] shell Pointer to the shell instance. + * @param[in] arg_cnt_ok Flag indicating valid number of arguments. + * @param[in] opt Pointer to the optional option array. + * @param[in] opt_len Option array size. + * + * @return True if check passed, false otherwise or help was requested. + */ +bool shell_cmd_precheck(const struct shell *shell, + bool arg_cnt_nok, + const struct shell_getopt_option *opt, + size_t opt_len); + +/* + * @internal @brief This function shall not be used directly, it is required by + * the fprintf module. + * + * @param[in] p_user_ctx Pointer to the context for the shell instance. + * @param[in] p_data Pointer to the data buffer. + * @param[in] data_len Data buffer size. + */ +void shell_print_stream(const void *user_ctx, const char *data, + size_t data_len); + +/* @} */ #ifdef __cplusplus } #endif -#endif /* ZEPHYR_INCLUDE_SHELL_SHELL_H_ */ +#endif /* SHELL_H__ */ diff --git a/include/shell/shell_fprintf.h b/include/shell/shell_fprintf.h new file mode 100644 index 000000000000..a92c207e5a42 --- /dev/null +++ b/include/shell/shell_fprintf.h @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SHELL_FPRINTF_H__ +#define SHELL_FPRINTF_H__ + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef void (*shell_fprintf_fwrite)(const void *user_ctx, + const char *data, + size_t length); + +struct shell_fprintf_control_block { + size_t buffer_cnt; + bool autoflush; +}; +/** + * @brief fprintf context + */ +struct shell_fprintf { + u8_t *buffer; + size_t buffer_size; + shell_fprintf_fwrite fwrite; + const void *user_ctx; + struct shell_fprintf_control_block *ctrl_blk; +}; + + +/** + * @brief Macro for defining shell_fprintf instance. + * + * @param _name Instance name. + * @param _user_ctx Pointer to user data. + * @param _buf Pointer to output buffer + * @param _size Size of output buffer. + * @param _autoflush Indicator if buffer shall be automatically flush. + * @param _fwrite Pointer to function sending data stream. + */ +#define SHELL_FPRINTF_DEFINE(_name, _user_ctx, _buf, _size, \ + _autoflush, _fwrite) \ + static struct shell_fprintf_control_block \ + _name##_shell_fprintf_ctx = { \ + .autoflush = _autoflush, \ + .buffer_cnt = 0 \ + }; \ + static const struct shell_fprintf _name = { \ + .buffer = _buf, \ + .buffer_size = _size, \ + .fwrite = _fwrite, \ + .user_ctx = _user_ctx, \ + .ctrl_blk = &_name##_shell_fprintf_ctx \ + } + +/** + * @brief fprintf like function which send formated data stream to output. + * + * @param sh_fprintf fprintf instance. + * @param fmt Format string. + * @param args List of parameters to print. + */ +void shell_fprintf_fmt(const struct shell_fprintf *sh_fprintf, + char const *fmt, va_list args); + +/** + * @brief function flushing data stored in io_buffer. + * + * @param sh_fprintf fprintf instance + */ +void shell_fprintf_buffer_flush(const struct shell_fprintf *sh_fprintf); + + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_FPRINTF_H__ */ diff --git a/include/shell/shell_history.h b/include/shell/shell_history.h new file mode 100644 index 000000000000..33ebdc4bd283 --- /dev/null +++ b/include/shell/shell_history.h @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SHELL_HISTORY_H__ +#define SHELL_HISTORY_H__ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + + +struct shell_history { + struct k_mem_slab *mem_slab; + sys_dlist_t list; + sys_dnode_t *current; +}; +#if CONFIG_SHELL_HISTORY +#define SHELL_HISTORY_DEFINE(_name, block_size, block_count) \ + \ + K_MEM_SLAB_DEFINE(_name##_history_memslab, \ + block_size, block_count, 4); \ + static struct shell_history _name##_history = { \ + .mem_slab = &_name##_history_memslab \ + } +#define SHELL_HISTORY_PTR(_name) (&_name##_history) +#else /* CONFIG_SHELL_HISTORY */ +#define SHELL_HISTORY_DEFINE(_name, block_size, block_count) /*empty*/ +#define SHELL_HISTORY_PTR(_name) NULL +#endif + + +void shell_history_init(struct shell_history *history); + +void shell_history_purge(struct shell_history *history); + +void shell_history_mode_exit(struct shell_history *history); + +/* returns true if remains in history mode.*/ +bool shell_history_get(struct shell_history *history, bool up, + u8_t *dst, size_t *len); + +void shell_history_put(struct shell_history *history, u8_t *line, size_t len); + +static inline bool shell_history_active(struct shell_history *history) +{ + return (history->current) ? true : false; +} + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_HISTORY_H__ */ diff --git a/include/shell/shell_log_backend.h b/include/shell/shell_log_backend.h new file mode 100644 index 000000000000..2c60de53c8a8 --- /dev/null +++ b/include/shell/shell_log_backend.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SHELL_LOG_BACKEND_H__ +#define SHELL_LOG_BACKEND_H__ + +#include +#include +#include +#include +#ifdef __cplusplus +extern "C" { +#endif + +extern const struct log_backend_api log_backend_shell_api; + +/** @brief Shell log backend states. */ +enum shell_log_backend_state { + SHELL_LOG_BACKEND_UNINIT, + SHELL_LOG_BACKEND_ENABLED, + SHELL_LOG_BACKEND_DISABLED, + SHELL_LOG_BACKEND_PANIC, +}; + +/** @brief Shell log backend control block (RW data). */ +struct shell_log_backend_control_block { + atomic_t cnt; + enum shell_log_backend_state state; +}; + +/** @brief Shell log backend instance structure (RO data). */ +struct shell_log_backend { + const struct log_backend *backend; + struct k_fifo *fifo; + const struct log_output *log_output; + struct shell_log_backend_control_block *control_block; +}; + +/** @brief Prototype of function outputing processed data. */ +int shell_log_backend_output_func(u8_t *data, size_t length, void *ctx); + +/** @def SHELL_LOG_BACKEND_DEFINE + * @brief Macro for creating instance of shell log backend. + * + * @param _name Shell name. + * @param _buf Output buffer. + * @param _size Output buffer size. + */ +/** @def SHELL_LOG_BACKEND_PTR + * @brief Macro for retrieving pointer to the instance of shell log backend. + * + * @param _name Shell name. + */ +#if CONFIG_LOG +#define SHELL_LOG_BACKEND_DEFINE(_name, _buf, _size) \ + LOG_BACKEND_DEFINE(_name##_backend, log_backend_shell_api); \ + K_FIFO_DEFINE(_name##_fifo); \ + LOG_OUTPUT_DEFINE(_name##_log_output, shell_log_backend_output_func, \ + _buf, _size); \ + static struct shell_log_backend_control_block _name##_control_block; \ + static const struct shell_log_backend _name##_log_backend = { \ + .backend = &_name##_backend, \ + .fifo = &_name##_fifo, \ + .log_output = &_name##_log_output, \ + .control_block = &_name##_control_block \ + } + +#define SHELL_LOG_BACKEND_PTR(_name) (&_name##_log_backend) +#else /* CONFIG_LOG */ +#define SHELL_LOG_BACKEND_DEFINE(_name, _buf, _size) /* empty */ +#define SHELL_LOG_BACKEND_PTR(_name) NULL +#endif /* CONFIG_LOG */ + +/** @brief Enable shell log backend. + * + * @param backend Shell log backend instance. + * @param ctx Pointer to shell instance. + * @param init_log_level Initial log level set to all logging sources. + */ +void shell_log_backend_enable(const struct shell_log_backend *backend, + void *ctx, u32_t init_log_level); + +/** @brief Disable shell log backend. + * + * @param backend Shell log backend instance. + */ +void shell_log_backend_disable(const struct shell_log_backend *backend); + +/** @brief Trigger processing of one log entry. + * + * @param backend Shell log backend instance. + * + * @return True if message was processed, false if FIFO was empty + */ +bool shell_log_backend_process(const struct shell_log_backend *backend); + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_LOG_BACKEND_H__ */ diff --git a/include/shell/shell_types.h b/include/shell/shell_types.h new file mode 100644 index 000000000000..9a34c05c706f --- /dev/null +++ b/include/shell/shell_types.h @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef SHELL_TYPES_H__ +#define SHELL_TYPES_H__ + + +#ifdef __cplusplus +extern "C" { +#endif + +enum shell_vt100_color { + SHELL_VT100_COLOR_DEFAULT, + SHELL_VT100_COLOR_BLACK, + SHELL_VT100_COLOR_RED, + SHELL_VT100_COLOR_GREEN, + SHELL_VT100_COLOR_YELLOW, + SHELL_VT100_COLOR_BLUE, + SHELL_VT100_COLOR_MAGENTA, + SHELL_VT100_COLOR_CYAN, + SHELL_VT100_COLOR_WHITE, + + VT100_COLOR_END +}; + +struct shell_vt100_colors { + enum shell_vt100_color col; /* Text color. */ + enum shell_vt100_color bgcol; /* Background color. */ +}; + +struct shell_multiline_cons { + u16_t cur_x; /* horizontal cursor position in edited command line.*/ + u16_t cur_x_end; /* horizontal cursor position at the end of command.*/ + u16_t cur_y; /* vertical cursor position in edited command.*/ + u16_t cur_y_end; /* vertical cursor position at the end of command.*/ + u16_t terminal_hei; /* terminal screen height.*/ + u16_t terminal_wid; /* terminal screen width.*/ + u8_t name_len; /*! + +#ifdef __cplusplus +extern "C" { +#endif + +extern const struct shell_transport_api shell_uart_transport_api; + +struct shell_uart { + struct device *dev; + shell_transport_handler_t handler; + struct k_timer timer; + void *context; + u8_t rx[1]; + size_t rx_cnt; +}; + +#define SHELL_UART_DEFINE(_name) \ + static struct shell_uart _name##_shell_uart; \ + struct shell_transport _name = { \ + .api = &shell_uart_transport_api, \ + .ctx = (struct shell_uart *)&_name##_shell_uart \ + } + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_UART_H__ */ diff --git a/samples/drivers/flash_shell/src/main.c b/samples/drivers/flash_shell/src/main.c index a1fe31e37f24..efffadabef07 100644 --- a/samples/drivers/flash_shell/src/main.c +++ b/samples/drivers/flash_shell/src/main.c @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include #include diff --git a/samples/mpu/mpu_test/src/main.c b/samples/mpu/mpu_test/src/main.c index 049e1f5f48ad..0ecc428a8a66 100644 --- a/samples/mpu/mpu_test/src/main.c +++ b/samples/mpu/mpu_test/src/main.c @@ -8,7 +8,7 @@ #include #include #include -#include +#include /* Assumption: our devices have less than 64MB of memory */ #define RESERVED_MEM_MAP (CONFIG_SRAM_BASE_ADDRESS + 0x4000000) diff --git a/samples/net/rpl_border_router/src/shell.c b/samples/net/rpl_border_router/src/shell.c index 23cbddbb2526..819a125dd6c0 100644 --- a/samples/net/rpl_border_router/src/shell.c +++ b/samples/net/rpl_border_router/src/shell.c @@ -11,7 +11,7 @@ #endif #include -#include +#include #include #include "../../../subsys/net/ip/rpl.h" diff --git a/samples/net/syslog_net/src/main.c b/samples/net/syslog_net/src/main.c index 3803258074aa..f33d020cd7c8 100644 --- a/samples/net/syslog_net/src/main.c +++ b/samples/net/syslog_net/src/main.c @@ -12,7 +12,7 @@ #include -#include +#include #include #include diff --git a/samples/net/wpanusb/src/wpanusb.c b/samples/net/wpanusb/src/wpanusb.c index b4ff68df6478..b612221bf53d 100644 --- a/samples/net/wpanusb/src/wpanusb.c +++ b/samples/net/wpanusb/src/wpanusb.c @@ -13,7 +13,7 @@ #include #include -#include +#include #include #include diff --git a/samples/net/zperf/src/zperf_shell.c b/samples/net/zperf/src/zperf_shell.c index d9fdbb8f2b43..4ae9c66d70b0 100644 --- a/samples/net/zperf/src/zperf_shell.c +++ b/samples/net/zperf/src/zperf_shell.c @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include diff --git a/samples/subsys/shell/shell_module/prj.conf b/samples/subsys/shell/shell_module/prj.conf index 09b08cee81ec..6fdc670dee1a 100644 --- a/samples/subsys/shell/shell_module/prj.conf +++ b/samples/subsys/shell/shell_module/prj.conf @@ -1,6 +1,8 @@ CONFIG_PRINTK=y -CONFIG_CONSOLE_SHELL=y -CONFIG_KERNEL_SHELL=y +CONFIG_SHELL=y +CONFIG_LOG=y +CONFIG_LOG_BACKEND_UART=n +CONFIG_KERNEL_SHELL=n CONFIG_OBJECT_TRACING=y CONFIG_THREAD_MONITOR=y CONFIG_INIT_STACKS=y diff --git a/samples/subsys/shell/shell_module/prj_minimal.conf b/samples/subsys/shell/shell_module/prj_minimal.conf new file mode 100644 index 000000000000..cc2a095c41fb --- /dev/null +++ b/samples/subsys/shell/shell_module/prj_minimal.conf @@ -0,0 +1,13 @@ +CONFIG_PRINTK=y +CONFIG_SHELL=y +CONFIG_LOG_BACKEND_UART=n +CONFIG_KERNEL_SHELL=n +CONFIG_OBJECT_TRACING=y +CONFIG_THREAD_MONITOR=y +CONFIG_INIT_STACKS=y +CONFIG_BOOT_BANNER=n + +CONFIG_LOG=n +CONFIG_SHELL_HISTORY=n +CONFIG_CONSOLE_SHELL_STACKSIZE=1024 +CONFIG_SHELL_CMD_BUFF_SIZE=128 diff --git a/samples/subsys/shell/shell_module/sample.yaml b/samples/subsys/shell/shell_module/sample.yaml index 9a49d4fd18c4..bdea3280d0f6 100644 --- a/samples/subsys/shell/shell_module/sample.yaml +++ b/samples/subsys/shell/shell_module/sample.yaml @@ -3,6 +3,13 @@ sample: tests: test: filter: ( CONFIG_UART_CONSOLE and CONFIG_SERIAL_SUPPORT_INTERRUPT ) - or CONFIG_NATIVE_POSIX_STDIN_CONSOLE tags: samples shell + platform_whitelist: quark_se_c1000_devboard galileo + frdm_k64f qemu_cortex_m3 qemu_x86 nrf52_pca10040 nrf51_pca10028 harness: keyboard + test_minimal: + filter: ( CONFIG_UART_CONSOLE and CONFIG_SERIAL_SUPPORT_INTERRUPT ) + tags: samples shell + harness: keyboard + extra_args: CONF_FILE="prj_minimal.conf" + diff --git a/samples/subsys/shell/shell_module/src/dynamic_cmd.c b/samples/subsys/shell/shell_module/src/dynamic_cmd.c new file mode 100644 index 000000000000..eb930a8bfe5e --- /dev/null +++ b/samples/subsys/shell/shell_module/src/dynamic_cmd.c @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#define MAX_CMD_CNT (20u) +#define MAX_CMD_LEN (33u) + +/* buffer holding dynamicly created user commands */ +static char dynamic_cmd_buffer[MAX_CMD_CNT][MAX_CMD_LEN]; +/* commands counter */ +static u8_t dynamic_cmd_cnt; + +typedef int cmp_t(const void *, const void *); +extern void qsort(void *a, size_t n, size_t es, cmp_t *cmp); + +static void cmd_dynamic(const struct shell *shell, size_t argc, char **argv) +{ + if ((argc == 1) || shell_help_requested(shell)) { + shell_help_print(shell, NULL, 0); + return; + } + + if (argc > 2) { + shell_fprintf(shell, SHELL_ERROR, + "%s: bad parameter count\r\n", argv[0]); + } else { + shell_fprintf(shell, SHELL_ERROR, + "%s: please specify subcommand\r\n", argv[0]); + } +} + +/* function required by qsort */ +static int string_cmp(const void *p_a, const void *p_b) +{ + return strcmp((const char *)p_a, (const char *)p_b); +} + +static void cmd_dynamic_add(const struct shell *shell, + size_t argc, char **argv) +{ + u8_t idx; + u16_t cmd_len; + + if (shell_help_requested(shell)) { + shell_help_print(shell, NULL, 0); + return; + } + + if (argc != 2) { + shell_fprintf(shell, SHELL_ERROR, + "%s: bad parameter count\r\n", argv[0]); + return; + } + + if (dynamic_cmd_cnt >= MAX_CMD_CNT) { + shell_fprintf(shell, SHELL_ERROR, "command limit reached\r\n"); + return; + } + + cmd_len = strlen(argv[1]); + + if (cmd_len >= MAX_CMD_LEN) { + shell_fprintf(shell, SHELL_ERROR, "too long command\r\n"); + return; + } + + for (idx = 0; idx < cmd_len; idx++) { + if (!isalnum((int)(argv[1][idx]))) { + shell_fprintf(shell, SHELL_ERROR, + "bad command name - please use only" + " alphanumerical characters\r\n"); + return; + } + } + + for (idx = 0; idx < MAX_CMD_CNT; idx++) { + if (!strcmp(dynamic_cmd_buffer[idx], argv[1])) { + shell_fprintf(shell, SHELL_ERROR, + "duplicated command\r\n"); + return; + } + } + + sprintf(dynamic_cmd_buffer[dynamic_cmd_cnt++], "%s", argv[1]); + + qsort(dynamic_cmd_buffer, dynamic_cmd_cnt, + sizeof(dynamic_cmd_buffer[0]), string_cmp); + + shell_fprintf(shell, SHELL_NORMAL, "command added successfully\r\n"); +} + +static void cmd_dynamic_show(const struct shell *shell, + size_t argc, char **argv) +{ + if (shell_help_requested(shell)) { + shell_help_print(shell, NULL, 0); + return; + } + + if (argc != 1) { + shell_fprintf(shell, SHELL_ERROR, + "%s: bad parameter count\r\n", argv[0]); + return; + } + + if (dynamic_cmd_cnt == 0) { + shell_fprintf(shell, SHELL_WARNING, + "Please add some commands first.\r\n"); + return; + } + + shell_fprintf(shell, SHELL_NORMAL, "Dynamic command list:\r\n"); + + for (u8_t i = 0; i < dynamic_cmd_cnt; i++) { + shell_fprintf(shell, SHELL_NORMAL, + "[%3d] %s\r\n", i, dynamic_cmd_buffer[i]); + } +} + +static void cmd_dynamic_execute(const struct shell *shell, + size_t argc, char **argv) +{ + if (shell_help_requested(shell)) { + shell_help_print(shell, NULL, 0); + return; + } + + if (argc != 2) { + shell_fprintf(shell, SHELL_ERROR, + "%s: bad parameter count\r\n", argv[0]); + return; + } + + for (u8_t idx = 0; idx < dynamic_cmd_cnt; idx++) { + if (!strcmp(dynamic_cmd_buffer[idx], argv[1])) { + shell_fprintf(shell, SHELL_NORMAL, + "dynamic command: %s\r\n", argv[1]); + return; + } + } + + shell_fprintf(shell, SHELL_ERROR, + "%s: uknown parameter: %s\r\n", argv[0], argv[1]); +} + +static void cmd_dynamic_remove(const struct shell *shell, size_t argc, + char **argv) +{ + if ((argc == 1) || shell_help_requested(shell)) { + shell_help_print(shell, NULL, 0); + return; + } + + if (argc != 2) { + shell_fprintf(shell, SHELL_ERROR, + "%s: bad parameter count\r\n", argv[0]); + return; + } + + for (u8_t idx = 0; idx < dynamic_cmd_cnt; idx++) { + if (!strcmp(dynamic_cmd_buffer[idx], argv[1])) { + if (idx == MAX_CMD_CNT - 1) { + dynamic_cmd_buffer[idx][0] = '\0'; + } else { + memmove(dynamic_cmd_buffer[idx], + dynamic_cmd_buffer[idx + 1], + sizeof(dynamic_cmd_buffer[idx]) * + (dynamic_cmd_cnt - idx)); + } + + --dynamic_cmd_cnt; + shell_fprintf(shell, SHELL_NORMAL, + "command removed successfully\r\n"); + return; + } + } + shell_fprintf(shell, SHELL_ERROR, + "did not find command: %s\r\n", argv[1]); +} + +/* dynamic command creation */ +static void dynamic_cmd_get(size_t idx, struct shell_static_entry *entry) +{ + if (idx < dynamic_cmd_cnt) { + /* m_dynamic_cmd_buffer must be sorted alphabetically to ensure + * correct CLI completion + */ + entry->syntax = dynamic_cmd_buffer[idx]; + entry->handler = NULL; + entry->subcmd = NULL; + entry->help = "Show dynamic command name."; + } else { + /* if there are no more dynamic commands available syntax + * must be set to NULL. + */ + entry->syntax = NULL; + } +} + +SHELL_CREATE_DYNAMIC_CMD(m_sub_dynamic_set, dynamic_cmd_get); +SHELL_CREATE_STATIC_SUBCMD_SET(m_sub_dynamic) +{ + SHELL_CMD(add, NULL, + "Add a new dynamic command.\nExample usage: [ dynamic add test " + "] will add a dynamic command 'test'.\nIn this example, command" + " name length is limited to 32 chars. You can add up to 20" + " commands. Commands are automatically sorted to ensure correct" + " shell completion.", + cmd_dynamic_add), + SHELL_CMD(execute, &m_sub_dynamic_set, + "Execute a command.", cmd_dynamic_execute), + SHELL_CMD(remove, &m_sub_dynamic_set, + "Remove a command.", cmd_dynamic_remove), + SHELL_CMD(show, NULL, + "Show all added dynamic commands.", cmd_dynamic_show), + SHELL_SUBCMD_SET_END +}; + +SHELL_CMD_REGISTER(dynamic, &m_sub_dynamic, + "Demonstrate dynamic command usage.", cmd_dynamic); diff --git a/samples/subsys/shell/shell_module/src/main.c b/samples/subsys/shell/shell_module/src/main.c index a50f9062d089..9f9a10a6955f 100644 --- a/samples/subsys/shell/shell_module/src/main.c +++ b/samples/subsys/shell/shell_module/src/main.c @@ -7,52 +7,127 @@ #include #include #include +#include #include +#include +#include -static int shell_cmd_ping(int argc, char *argv[]) +LOG_MODULE_REGISTER(app); + +SHELL_UART_DEFINE(shell_transport_uart); +SHELL_DEFINE(uart_shell, "uart:~$ ", &shell_transport_uart, '\r', 10); + +extern void foo(void); + +void timer_expired_handler(struct k_timer *timer) +{ + LOG_INF("Timer expired."); + + /* Call another module to present logging from multiple sources. */ + foo(); +} + +K_TIMER_DEFINE(log_timer, timer_expired_handler, NULL); + +static void cmd_log_test_start(const struct shell *shell, size_t argc, + char **argv, u32_t period) +{ + if (!shell_cmd_precheck(shell, argc == 1, NULL, 0)) { + return; + } + + k_timer_start(&log_timer, period, period); + shell_fprintf(shell, SHELL_NORMAL, "Log test started\r\n"); +} + +static void cmd_log_test_start_demo(const struct shell *shell, size_t argc, + char **argv) +{ + cmd_log_test_start(shell, argc, argv, 200); +} + +static void cmd_log_test_start_flood(const struct shell *shell, size_t argc, + char **argv) +{ + cmd_log_test_start(shell, argc, argv, 10); +} + +static void cmd_log_test_stop(const struct shell *shell, size_t argc, + char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); - printk("pong\n"); + if (!shell_cmd_precheck(shell, argc == 1, NULL, 0)) { + return; + } - return 0; + k_timer_stop(&log_timer); + shell_fprintf(shell, SHELL_NORMAL, "Log test stopped\r\n"); } +SHELL_CREATE_STATIC_SUBCMD_SET(sub_log_test_start) +{ + /* Alphabetically sorted. */ + SHELL_CMD(demo, NULL, + "Start log timer which generates log message every 200ms.", + cmd_log_test_start_demo), + SHELL_CMD(flood, NULL, + "Start log timer which generates log message every 10ms.", + cmd_log_test_start_flood), + SHELL_SUBCMD_SET_END /* Array terminated. */ +}; +SHELL_CREATE_STATIC_SUBCMD_SET(sub_log_test) +{ + /* Alphabetically sorted. */ + SHELL_CMD(start, &sub_log_test_start, "Start log test", NULL), + SHELL_CMD(stop, NULL, "Stop log test.", cmd_log_test_stop), + SHELL_SUBCMD_SET_END /* Array terminated. */ +}; -static int shell_cmd_params(int argc, char *argv[]) +SHELL_CMD_REGISTER(log_test, &sub_log_test, "Log test", NULL); + +static void cmd_demo_ping(const struct shell *shell, size_t argc, char **argv) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + shell_fprintf(shell, SHELL_NORMAL, "pong\r\n"); +} + +static void cmd_demo_params(const struct shell *shell, size_t argc, char **argv) { int cnt; - printk("argc = %d\n", argc); + shell_fprintf(shell, SHELL_NORMAL, "argc = %d\r\n", argc); for (cnt = 0; cnt < argc; cnt++) { - printk(" argv[%d] = %s\n", cnt, argv[cnt]); + shell_fprintf(shell, SHELL_NORMAL, + " argv[%d] = %s\r\n", cnt, argv[cnt]); } - return 0; } -#define SHELL_CMD_VERSION "version" -static int shell_cmd_version(int argc, char *argv[]) +static void cmd_version(const struct shell *shell, size_t argc, char **argv) { ARG_UNUSED(argc); ARG_UNUSED(argv); - printk("Zephyr version %s\n", KERNEL_VERSION_STRING); - return 0; + shell_fprintf(shell, SHELL_NORMAL, + "Zephyr version %s\r\n", KERNEL_VERSION_STRING); } -SHELL_REGISTER_COMMAND(SHELL_CMD_VERSION, shell_cmd_version, - "Show kernel version"); +SHELL_CREATE_STATIC_SUBCMD_SET(sub_demo) +{ + /* Alphabetically sorted. */ + SHELL_CMD(params, NULL, "Print params command.", cmd_demo_params), + SHELL_CMD(ping, NULL, "Ping command.", cmd_demo_ping), + SHELL_SUBCMD_SET_END /* Array terminated. */ +}; -#define MY_SHELL_MODULE "sample_module" +SHELL_CMD_REGISTER(demo, &sub_demo, "Demo commands", NULL); -static struct shell_cmd commands[] = { - { "ping", shell_cmd_ping, NULL }, - { "params", shell_cmd_params, "print argc" }, - { NULL, NULL, NULL } -}; +SHELL_CMD_REGISTER(version, NULL, "Show kernel version", cmd_version); void main(void) { - SHELL_REGISTER(MY_SHELL_MODULE, commands); + (void)shell_init(&uart_shell, NULL, true, true, LOG_LEVEL_INF); } diff --git a/samples/subsys/shell/shell_module/src/qsort.c b/samples/subsys/shell/shell_module/src/qsort.c new file mode 100644 index 000000000000..86682a9d7dc3 --- /dev/null +++ b/samples/subsys/shell/shell_module/src/qsort.c @@ -0,0 +1,196 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1992, 1993 + * The Regents of the University of California. 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 University 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 REGENTS 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 REGENTS 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. + */ + +#if defined(LIBC_SCCS) && !defined(lint) +static const char sccsid[] = "@(#)qsort.c 8.1 (Berkeley) 6/4/93"; +#endif /* LIBC_SCCS and not lint */ +#include +#include + +#ifdef I_AM_QSORT_R +typedef int cmp_t(void *, const void *, const void *); +#else +typedef int cmp_t(const void *, const void *); +#endif +static inline char *med3(char *, char *, char *, cmp_t *, void *); + +#define MIN(a, b) ((a) < (b) ? a : b) + +/* + * Qsort routine from Bentley & McIlroy's "Engineering a Sort Function". + */ + +static inline void +swapfunc(char *a, char *b, size_t es) +{ + char t; + + do { + t = *a; + *a++ = *b; + *b++ = t; + } while (--es > 0); +} + +#define vecswap(a, b, n) \ + do { \ + if ((n) > 0) { \ + swapfunc(a, b, n); \ + } \ + } while (0) + +#ifdef I_AM_QSORT_R +#define CMP(t, x, y) (cmp((t), (x), (y))) +#else +#define CMP(t, x, y) (cmp((x), (y))) +#endif + +static inline char * +med3(char *a, char *b, char *c, cmp_t *cmp, void *thunk) +{ + return CMP(thunk, a, b) < 0 ? + (CMP(thunk, b, c) < 0 ? b : (CMP(thunk, a, c) < 0 ? c : a)) + : (CMP(thunk, b, c) > 0 ? b : (CMP(thunk, a, c) < 0 ? a : c)); +} + +#ifdef I_AM_QSORT_R +void qsort_r(void *a, size_t n, size_t es, void *thunk, cmp_t *cmp) +#else +#define thunk NULL +void qsort(void *a, size_t n, size_t es, cmp_t *cmp) +#endif +{ + char *pa, *pb, *pc, *pd, *pl, *pm, *pn; + size_t d1, d2; + int cmp_result; + int swap_cnt; + +loop: + swap_cnt = 0; + if (n < 7) { + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; + pl > (char *)a && CMP(thunk, pl - es, pl) > 0; + pl -= es) + swapfunc(pl, pl - es, es); + return; + } + pm = (char *)a + (n / 2) * es; + if (n > 7) { + pl = a; + pn = (char *)a + (n - 1) * es; + if (n > 40) { + size_t d = (n / 8) * es; + + pl = med3(pl, pl + d, pl + 2 * d, cmp, thunk); + pm = med3(pm - d, pm, pm + d, cmp, thunk); + pn = med3(pn - 2 * d, pn - d, pn, cmp, thunk); + } + pm = med3(pl, pm, pn, cmp, thunk); + } + swapfunc(a, pm, es); + pa = pb = (char *)a + es; + + pc = pd = (char *)a + (n - 1) * es; + for (;;) { + while (pb <= pc && (cmp_result = CMP(thunk, pb, a)) <= 0) { + if (cmp_result == 0) { + swap_cnt = 1; + swapfunc(pa, pb, es); + pa += es; + } + pb += es; + } + while (pb <= pc && (cmp_result = CMP(thunk, pc, a)) >= 0) { + if (cmp_result == 0) { + swap_cnt = 1; + swapfunc(pc, pd, es); + pd -= es; + } + pc -= es; + } + if (pb > pc) + break; + swapfunc(pb, pc, es); + swap_cnt = 1; + pb += es; + pc -= es; + } + if (swap_cnt == 0) { /* Switch to insertion sort */ + for (pm = (char *)a + es; pm < (char *)a + n * es; pm += es) + for (pl = pm; + pl > (char *)a && CMP(thunk, pl - es, pl) > 0; + pl -= es) + swapfunc(pl, pl - es, es); + return; + } + + pn = (char *)a + n * es; + d1 = MIN(pa - (char *)a, pb - pa); + vecswap(a, pb - d1, d1); + d1 = MIN(pd - pc, pn - pd - es); + vecswap(pb, pn - d1, d1); + + d1 = pb - pa; + d2 = pd - pc; + if (d1 <= d2) { + /* Recurse on left partition, then iterate on right partition */ + if (d1 > es) { +#ifdef I_AM_QSORT_R + qsort_r(a, d1 / es, es, thunk, cmp); +#else + qsort(a, d1 / es, es, cmp); +#endif + } + if (d2 > es) { + /* Iterate rather than recurse to save stack space */ + /* qsort(pn - d2, d2 / es, es, cmp); */ + a = pn - d2; + n = d2 / es; + goto loop; + } + } else { + /* Recurse on right partition, then iterate on left partition */ + if (d2 > es) { +#ifdef I_AM_QSORT_R + qsort_r(pn - d2, d2 / es, es, thunk, cmp); +#else + qsort(pn - d2, d2 / es, es, cmp); +#endif + } + if (d1 > es) { + /* Iterate rather than recurse to save stack space */ + /* qsort(a, d1 / es, es, cmp); */ + n = d1 / es; + goto loop; + } + } +} diff --git a/samples/subsys/shell/shell_module/src/test_module.c b/samples/subsys/shell/shell_module/src/test_module.c new file mode 100644 index 000000000000..69e87a9ee860 --- /dev/null +++ b/samples/subsys/shell/shell_module/src/test_module.c @@ -0,0 +1,15 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +LOG_MODULE_REGISTER(app_test); + +void foo(void) +{ + LOG_INF("info message"); + LOG_WRN("warning message"); + LOG_ERR("err message"); +} diff --git a/scripts/sanitycheck b/scripts/sanitycheck index 522c5c9b65f5..fc47ab6d1c3d 100755 --- a/scripts/sanitycheck +++ b/scripts/sanitycheck @@ -654,7 +654,7 @@ class SizeCalculator: "kobject_data", "mmu_tables", "app_pad", "priv_stacks", "ccm_data", "usb_descriptor", "usb_data", "usb_bos_desc", 'log_backends_sections', 'log_dynamic_sections', - 'log_const_sections',"app_smem"] + 'log_const_sections',"app_smem", 'shell_root_cmds_sections'] # These get copied into RAM only on non-XIP ro_sections = ["text", "ctors", "init_array", "reset", "object_access", "rodata", "devconfig", "net_l2", "vector", "_bt_settings_area"] diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 50b74b669752..de436f0a0ffd 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(logging) add_subdirectory_ifdef(CONFIG_BT bluetooth) add_subdirectory_ifdef(CONFIG_CONSOLE_SUBSYS console) add_subdirectory_ifdef(CONFIG_CONSOLE_SHELL shell) +add_subdirectory_ifdef(CONFIG_SHELL shell) add_subdirectory_ifdef(CONFIG_CPLUSPLUS cpp) add_subdirectory_ifdef(CONFIG_DISK_ACCESS disk) add_subdirectory(fs) diff --git a/subsys/bluetooth/host/mesh/shell.c b/subsys/bluetooth/host/mesh/shell.c index 47a31952b86e..84cda5cbeac1 100644 --- a/subsys/bluetooth/host/mesh/shell.c +++ b/subsys/bluetooth/host/mesh/shell.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include diff --git a/subsys/bluetooth/shell/bt.c b/subsys/bluetooth/shell/bt.c index 21f48dfda0a1..240645fb9509 100644 --- a/subsys/bluetooth/shell/bt.c +++ b/subsys/bluetooth/shell/bt.c @@ -28,7 +28,7 @@ #include #include -#include +#include #include "bt.h" #include "gatt.h" diff --git a/subsys/bluetooth/shell/flash.c b/subsys/bluetooth/shell/flash.c index e964f2803b49..35415ece4332 100644 --- a/subsys/bluetooth/shell/flash.c +++ b/subsys/bluetooth/shell/flash.c @@ -10,7 +10,7 @@ */ #include -#include +#include #include #include diff --git a/subsys/bluetooth/shell/ll.c b/subsys/bluetooth/shell/ll.c index 7b6bab9c3182..2f0d680bc3a1 100644 --- a/subsys/bluetooth/shell/ll.c +++ b/subsys/bluetooth/shell/ll.c @@ -12,7 +12,7 @@ #include #include #include -#include +#include #include #include diff --git a/subsys/bluetooth/shell/ticker.c b/subsys/bluetooth/shell/ticker.c index 8d8d49613afe..5321cd646959 100644 --- a/subsys/bluetooth/shell/ticker.c +++ b/subsys/bluetooth/shell/ticker.c @@ -10,7 +10,7 @@ */ #include -#include +#include #include #if defined(CONFIG_SOC_FAMILY_NRF) diff --git a/subsys/fs/shell.c b/subsys/fs/shell.c index cd52925ce938..c4a01a7043b9 100644 --- a/subsys/fs/shell.c +++ b/subsys/fs/shell.c @@ -9,7 +9,7 @@ #include #include -#include +#include #include #include #include diff --git a/subsys/logging/CMakeLists.txt b/subsys/logging/CMakeLists.txt index a27cf1e2bf1f..e46c5e035f1e 100644 --- a/subsys/logging/CMakeLists.txt +++ b/subsys/logging/CMakeLists.txt @@ -15,6 +15,6 @@ zephyr_sources_ifdef( ) zephyr_sources_ifdef( - CONFIG_LOG_BACKEND_NATIVE_POSIX - log_backend_native_posix.c + CONFIG_LOG_CMDS + log_cmds.c ) diff --git a/subsys/logging/Kconfig b/subsys/logging/Kconfig index f47e15670855..b427ce69daa9 100644 --- a/subsys/logging/Kconfig +++ b/subsys/logging/Kconfig @@ -263,6 +263,10 @@ config LOG_DOMAIN_ID help In multicore system each application/core must have unique domain ID. +config LOG_CMDS + bool "Enable shell commands" + default y if SHELL + config LOG_BACKEND_UART bool "Enable UART backend" depends on UART_CONSOLE diff --git a/subsys/logging/log_cmds.c b/subsys/logging/log_cmds.c new file mode 100644 index 000000000000..7acf65498460 --- /dev/null +++ b/subsys/logging/log_cmds.c @@ -0,0 +1,454 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +typedef void (*log_backend_cmd_t)(const struct shell *shell, + const struct log_backend *backend, + size_t argc, + char **argv); + +static const char * const severity_lvls[] = { + "none", + "err", + "wrn", + "inf", + "dbg", +}; + +static const char * const severity_lvls_sorted[] = { + "dbg", + "err", + "inf", + "none", + "wrn", +}; + +/** + * @brief Function for finding backend instance with given name. + * + * @param p_name Name of the backend instance. + * + * @return Pointer to the instance or NULL. + * + */ +static const struct log_backend *backend_find(char const *name) +{ + const struct log_backend *backend; + size_t slen = strlen(name); + + for (int i = 0; i < log_backend_count_get(); i++) { + backend = log_backend_get(i); + if (strncmp(name, backend->name, slen) == 0) { + return backend; + } + } + + return NULL; +} + +static bool shell_state_precheck(const struct shell *shell) +{ + if (shell->log_backend->control_block->state + == SHELL_LOG_BACKEND_UNINIT) { + shell_fprintf(shell, SHELL_ERROR, + "Shell log backend not initialized.\r\n"); + return false; + } + + return true; +} + +/** + * @brief Function for executing command on given backend. + */ +static void shell_backend_cmd_execute(const struct shell *shell, + size_t argc, + char **argv, + log_backend_cmd_t func) +{ + /* Based on the structure of backend commands, name of the backend can + * be found at -1 (log backend command). + */ + char const *name = argv[-1]; + const struct log_backend *backend = backend_find(name); + + if (backend) { + func(shell, backend, argc, argv); + } else { + shell_fprintf(shell, SHELL_ERROR, + "Invalid backend: %s\r\n", name); + } +} + + +static void log_status(const struct shell *shell, + const struct log_backend *backend, + size_t argc, char **argv) +{ + u32_t modules_cnt = log_sources_count(); + u32_t dynamic_lvl; + u32_t compiled_lvl; + u32_t i; + + + if (!log_backend_is_active(backend)) { + shell_fprintf(shell, SHELL_ERROR, "Logs are halted!\r\n"); + } + + shell_fprintf(shell, SHELL_NORMAL, "%-40s | current | built-in \r\n", + "module_name"); + shell_fprintf(shell, SHELL_NORMAL, + "----------------------------------------------------------\r\n"); + + for (i = 0; i < modules_cnt; i++) { + dynamic_lvl = log_filter_get(backend, CONFIG_LOG_DOMAIN_ID, + i, true); + compiled_lvl = log_filter_get(backend, CONFIG_LOG_DOMAIN_ID, + i, false); + + shell_fprintf(shell, SHELL_NORMAL, "%-40s | %-7s | %s\r\n", + log_source_name_get(CONFIG_LOG_DOMAIN_ID, i), + severity_lvls[dynamic_lvl], + severity_lvls[compiled_lvl]); + } +} + + +static void cmd_log_self_status(const struct shell *shell, + size_t argc, char **argv) +{ + if (!shell_state_precheck(shell)) { + return; + } + + log_status(shell, shell->log_backend->backend, argc, argv); +} + +static void cmd_log_backend_status(const struct shell *shell, + size_t argc, char **argv) +{ + shell_backend_cmd_execute(shell, argc, argv, log_status); +} + +static int module_id_get(const char *name) +{ + u32_t modules_cnt = log_sources_count(); + const char *tmp_name; + u32_t i; + + for (i = 0; i < modules_cnt; i++) { + tmp_name = log_source_name_get(CONFIG_LOG_DOMAIN_ID, i); + + if (strncmp(tmp_name, name, 64) == 0) { + return i; + } + } + return -1; +} + +static u32_t module_filter_set(const struct log_backend *backend, + int module_id, u32_t level) +{ + u32_t compiled_lvl; + + compiled_lvl = log_filter_get(backend, CONFIG_LOG_DOMAIN_ID, + module_id, false); + if (level > compiled_lvl) { + level = compiled_lvl; + } + + log_filter_set(backend, CONFIG_LOG_DOMAIN_ID, module_id, level); + + return level; +} + +static void filters_set(const struct shell *shell, + const struct log_backend *backend, + size_t argc, char **argv, u32_t level) +{ + int i; + int id; + bool all = argc ? false : true; + int cnt = all ? log_sources_count() : argc; + + if (!backend->cb->active) { + shell_fprintf(shell, SHELL_WARNING, "Backend not active.\r\n"); + } + + for (i = 0; i < cnt; i++) { + id = all ? i : module_id_get(argv[i]); + if (id >= 0) { + u32_t set_lvl = module_filter_set(backend, id, level); + + if (set_lvl != level) { + const char *name; + + name = all ? + log_source_name_get( + CONFIG_LOG_DOMAIN_ID, i) : + argv[i]; + shell_fprintf(shell, SHELL_WARNING, + "%s: level set to %s.\r\n", + name, severity_lvls[set_lvl]); + } + } else { + shell_fprintf(shell, SHELL_ERROR, + "%s: unknown source name.\r\n", argv[i]); + } + } +} + +static int severity_level_get(const char *str) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(severity_lvls); i++) { + if (strncmp(str, severity_lvls[i], 4) == 0) { + return i; + } + } + + return -1; +} +static void log_enable(const struct shell *shell, + const struct log_backend *backend, + size_t argc, + char **argv) +{ + int severity_level; + + if (!shell_cmd_precheck(shell, (argc > 1), NULL, 0)) { + return; + } + + severity_level = severity_level_get(argv[1]); + + if (severity_level < 0) { + shell_fprintf(shell, SHELL_ERROR, "Invalid severity: %s\r\n", + argv[1]); + return; + } + + /* Arguments following severity level are interpreted as module names.*/ + filters_set(shell, backend, argc - 2, &argv[2], severity_level); +} + +static void cmd_log_self_enable(const struct shell *shell, + size_t argc, char **argv) +{ + if (!shell_state_precheck(shell)) { + return; + } + + log_enable(shell, shell->log_backend->backend, argc, argv); +} + +static void cmd_log_backend_enable(const struct shell *shell, + size_t argc, char **argv) +{ + shell_backend_cmd_execute(shell, argc, argv, log_enable); +} + +static void log_disable(const struct shell *shell, + const struct log_backend *backend, + size_t argc, + char **argv) +{ + if (!shell_cmd_precheck(shell, (argc > 1), NULL, 0)) { + return; + } + + filters_set(shell, backend, argc - 1, &argv[1], LOG_LEVEL_NONE); +} + +static void cmd_log_self_disable(const struct shell *shell, + size_t argc, char **argv) +{ + if (!shell_state_precheck(shell)) { + return; + } + + log_disable(shell, shell->log_backend->backend, argc, argv); +} + +static void cmd_log_backend_disable(const struct shell *shell, + size_t argc, char **argv) +{ + shell_backend_cmd_execute(shell, argc, argv, log_disable); +} + +static void module_name_get(size_t idx, struct shell_static_entry *entry); + +SHELL_CREATE_DYNAMIC_CMD(dsub_module_name, module_name_get); + +static void module_name_get(size_t idx, struct shell_static_entry *entry) +{ + entry->handler = NULL; + entry->help = NULL; + entry->subcmd = &dsub_module_name; + entry->syntax = log_source_name_get(CONFIG_LOG_DOMAIN_ID, idx); +} + + +static void severity_lvl_get(size_t idx, struct shell_static_entry *entry) +{ + entry->handler = NULL; + entry->help = NULL; + entry->subcmd = &dsub_module_name; + entry->syntax = (idx < ARRAY_SIZE(severity_lvls_sorted)) ? + severity_lvls_sorted[idx] : NULL; +} + +SHELL_CREATE_DYNAMIC_CMD(dsub_severity_lvl, severity_lvl_get); + +static void log_halt(const struct shell *shell, + const struct log_backend *backend, + size_t argc, + char **argv) +{ + log_backend_deactivate(backend); +} + + +static void cmd_log_self_halt(const struct shell *shell, + size_t argc, char **argv) +{ + if (!shell_state_precheck(shell)) { + return; + } + + log_halt(shell, shell->log_backend->backend, argc, argv); +} + +static void cmd_log_backend_halt(const struct shell *shell, + size_t argc, char **argv) +{ + shell_backend_cmd_execute(shell, argc, argv, log_halt); +} + +static void log_go(const struct shell *shell, + const struct log_backend *backend, + size_t argc, + char **argv) +{ + log_backend_activate(backend, backend->cb->ctx); +} + + +static void cmd_log_self_go(const struct shell *shell, + size_t argc, char **argv) +{ + if (!shell_state_precheck(shell)) { + return; + } + + log_go(shell, shell->log_backend->backend, argc, argv); +} + +static void cmd_log_backend_go(const struct shell *shell, + size_t argc, char **argv) +{ + shell_backend_cmd_execute(shell, argc, argv, log_go); +} + + +static void cmd_log_backends_list(const struct shell *shell, + size_t argc, char **argv) +{ + int backend_count; + + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + + backend_count = log_backend_count_get(); + + for (int i = 0; i < backend_count; i++) { + const struct log_backend *backend = log_backend_get(i); + + shell_fprintf(shell, SHELL_NORMAL, + "%s\r\n" + "\t- Status: %s\r\n" + "\t- ID: %d\r\n\r\n", + backend->name, + backend->cb->active ? "enabled" : "disabled", + backend->cb->id); + + } +} + + +SHELL_CREATE_STATIC_SUBCMD_SET(sub_log_backend) +{ + SHELL_CMD(disable, &dsub_module_name, + "'log disable .. ' disables logs in " + "specified modules (all if no modules specified).", + cmd_log_backend_disable), + SHELL_CMD(enable, &dsub_severity_lvl, + "'log enable ... ' enables logs" + " up to given level in specified modules (all if no modules " + "specified).", + cmd_log_backend_enable), + SHELL_CMD(go, NULL, "Resume logging", cmd_log_backend_go), + SHELL_CMD(halt, NULL, "Halt logging", cmd_log_backend_halt), + SHELL_CMD(status, NULL, "Logger status", cmd_log_backend_status), + SHELL_SUBCMD_SET_END +}; + +static void backend_name_get(size_t idx, struct shell_static_entry *entry) +{ + entry->handler = NULL; + entry->help = NULL; + entry->subcmd = &sub_log_backend; + entry->syntax = NULL; + + if (idx < log_backend_count_get()) { + const struct log_backend *backend = log_backend_get(idx); + + entry->syntax = backend->name; + } +} + +SHELL_CREATE_DYNAMIC_CMD(dsub_backend_name_dynamic, backend_name_get); + + +SHELL_CREATE_STATIC_SUBCMD_SET(sub_log_stat) +{ + SHELL_CMD(backend, &dsub_backend_name_dynamic, + "Logger backends commands.", NULL), + SHELL_CMD(disable, &dsub_module_name, + "'log disable .. ' disables logs in specified " + "modules (all if no modules specified).", + cmd_log_self_disable), + SHELL_CMD(enable, &dsub_severity_lvl, + "'log enable ... ' enables logs up to" + " given level in specified modules (all if no modules specified).", + cmd_log_self_enable), + SHELL_CMD(go, NULL, "Resume logging", cmd_log_self_go), + SHELL_CMD(halt, NULL, "Halt logging", cmd_log_self_halt), + SHELL_CMD(list_backends, NULL, "Lists logger backends.", + cmd_log_backends_list), + SHELL_CMD(status, NULL, "Logger status", cmd_log_self_status), + SHELL_SUBCMD_SET_END +}; + +static void cmd_log(const struct shell *shell, size_t argc, char **argv) +{ + if ((argc == 1) || shell_help_requested(shell)) { + shell_help_print(shell, NULL, 0); + return; + } + + shell_fprintf(shell, SHELL_ERROR, "%s:%s%s\r\n", + argv[0], " unknown parameter: ", argv[1]); +} + +SHELL_CMD_REGISTER(log, &sub_log_stat, "Commands for controlling logger", + cmd_log); diff --git a/subsys/logging/log_core.c b/subsys/logging/log_core.c index 4b2b7061edce..fd41cb090fc6 100644 --- a/subsys/logging/log_core.c +++ b/subsys/logging/log_core.c @@ -376,9 +376,7 @@ u32_t log_src_cnt_get(u32_t domain_id) const char *log_source_name_get(u32_t domain_id, u32_t src_id) { - assert(src_id < log_sources_count()); - - return log_name_get(src_id); + return src_id < log_sources_count() ? log_name_get(src_id) : NULL; } static u32_t max_filter_get(u32_t filters) diff --git a/subsys/mgmt/smp_shell.c b/subsys/mgmt/smp_shell.c index e619fa3da1a8..583431c7dbeb 100644 --- a/subsys/mgmt/smp_shell.c +++ b/subsys/mgmt/smp_shell.c @@ -12,7 +12,7 @@ #include #include #include "net/buf.h" -#include "shell/shell.h" +#include "shell/legacy_shell.h" #include "mgmt/mgmt.h" #include "mgmt/serial.h" #include "mgmt/buf.h" diff --git a/subsys/net/ip/net_shell.c b/subsys/net/ip/net_shell.c index 4e160f7a9c49..e2befb250815 100644 --- a/subsys/net/ip/net_shell.c +++ b/subsys/net/ip/net_shell.c @@ -13,7 +13,7 @@ #include #include #include -#include +#include #include #include diff --git a/subsys/net/l2/bluetooth/bluetooth_shell.c b/subsys/net/l2/bluetooth/bluetooth_shell.c index 716f8e6fc0ed..339a52b8538e 100644 --- a/subsys/net/l2/bluetooth/bluetooth_shell.c +++ b/subsys/net/l2/bluetooth/bluetooth_shell.c @@ -10,7 +10,7 @@ #include #include -#include +#include #include #include diff --git a/subsys/net/l2/ieee802154/ieee802154_shell.c b/subsys/net/l2/ieee802154/ieee802154_shell.c index 02091428fd1c..ca4fed563be4 100644 --- a/subsys/net/l2/ieee802154/ieee802154_shell.c +++ b/subsys/net/l2/ieee802154/ieee802154_shell.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include diff --git a/subsys/net/l2/wifi/wifi_shell.c b/subsys/net/l2/wifi/wifi_shell.c index e6f578bcb055..927aa16ab1b0 100644 --- a/subsys/net/l2/wifi/wifi_shell.c +++ b/subsys/net/l2/wifi/wifi_shell.c @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include diff --git a/subsys/net/lib/openthread/platform/shell.c b/subsys/net/lib/openthread/platform/shell.c index 1cfccc3f2980..71ecd0765b96 100644 --- a/subsys/net/lib/openthread/platform/shell.c +++ b/subsys/net/lib/openthread/platform/shell.c @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include diff --git a/subsys/shell/CMakeLists.txt b/subsys/shell/CMakeLists.txt index 6d629475a503..e0b9c2ad3199 100644 --- a/subsys/shell/CMakeLists.txt +++ b/subsys/shell/CMakeLists.txt @@ -2,9 +2,39 @@ zephyr_include_directories_ifdef(CONFIG_CONSOLE_SHELL ${ZEPHYR_BASE}/include/drivers ) -zephyr_sources( +zephyr_sources_ifdef( + CONFIG_CONSOLE_SHELL shell_service.c - shell.c + legacy_shell.c ) add_subdirectory(modules) + +zephyr_sources_ifdef( + CONFIG_SHELL + shell.c + shell_fprintf.c + shell_utils.c + shell_ops.c + shell_uart.c + ) + +zephyr_sources_ifdef( + CONFIG_SHELL_CMDS + shell_cmds.c +) + +zephyr_sources_ifdef( + CONFIG_SHELL_HISTORY + shell_history.c +) + +zephyr_sources_ifdef( + CONFIG_LOG + shell_log_backend.c +) + +zephyr_sources_ifdef( + CONFIG_SHELL_WILDCARD + shell_wildcard.c +) diff --git a/subsys/shell/Kconfig b/subsys/shell/Kconfig index 05ca22db93c9..a01ca5648191 100644 --- a/subsys/shell/Kconfig +++ b/subsys/shell/Kconfig @@ -7,9 +7,10 @@ # SPDX-License-Identifier: Apache-2.0 # +menu "Shell Options" -config CONSOLE_SHELL - bool "Enable console input handler [ Experimental ]" +menuconfig CONSOLE_SHELL + bool "Enable legacy shell [ Experimental ]" select CONSOLE_HANDLER select CONSOLE_SUBSYS help @@ -33,3 +34,142 @@ config CONSOLE_SHELL_MAX_CMD_QUEUED source "subsys/shell/modules/Kconfig" endif + +menuconfig SHELL + bool "Enable shell" + select LOG_RUNTIME_FILTERING + select POLL + +if SHELL + +module = SHELL +module-str = Shell +source "subsys/logging/Kconfig.template.log_config" + +config SHELL_STACK_SIZE + int "Shell thread stack size" + default 2048 if MULTITHREADING + default 0 if !MULTITHREADING + help + Stack size for thread created for each instance. + +config SHELL_THREAD_PRIO + int "Shell thread priority" + depends on MULTITHREADING + default -2 + help + Shell thread priority. + +config SHELL_BACKSPACE_MODE_DELETE + bool "Default escape code for backspace is DELETE (0x7F)" + default y + help + Terminals have different escape code settings for backspace button. + Some terminals send code: 0x08 (backspace) other 0x7F (delete). When + this option is set shell will expect 0x7F for backspace key. + +config SHELL_CMD_BUFF_SIZE + int "Shell command buffer size" + default 256 + help + Maximum command size. + +config SHELL_PRINTF_BUFF_SIZE + int "Shell print buffer size" + default 30 + help + Maximum text buffer size for fprintf function. + It is working like stdio buffering in Linux systems + to limit number of peripheral access calls. + +config SHELL_ARGC_MAX + int "Maximum arguments in shell command" + default 12 + help + Maximum number of arguments that can build a command. + If command is composed of more than defined, argument SHELL_ARGC_MAX + and following are passed as one argument in the string. + +config SHELL_WILDCARD + bool "Enable wildcard support in shell" + select FNMATCH + help + Enables using * in shell. + +config SHELL_ECHO_STATUS + bool "Enable echo on shell" + default y + help + If enabled shell prints back every input byte. + +config SHELL_VT100_COLORS + bool "Enable colors in shell" + default y + help + If enabled VT100 colors are used in shell (e.g. print errors in red). + +config SHELL_METAKEYS + bool "Enable metakeys" + default y + help + Enables shell metakeys: Home, End, ctrl+a, ctrl+c, ctrl+e, ctrl+l, + ctrl+u, ctrl+w + +config SHELL_HELP + bool "Enable help message" + default y + help + Enables formatting help message when requested with '-h' or '--help'. + +config SHELL_HELP_ON_WRONG_ARGUMENT_COUNT + bool "Enable printing help on wrong argument count" + depends on SHELL_HELP + default y + +config SHELL_HISTORY + bool "Enable history in shell" + default y + help + Enable commands history. History can be accessed using up and down + arrows + +if SHELL_HISTORY + +config SHELL_HISTORY_BUFFER + int "History buffer in bytes" + default 1024 + help + Number of bytes dedicated for storing executed commands. + +endif #SHELL_HISTORY + +config SHELL_MAX_LOG_MSG_BUFFERED + int "Maximal number of log messages in FIFO" + default 8 + help + When amount of buffered log messages exceeds this threshold oldest + messages are discarded. + +config SHELL_STATS + bool "Enable shell statistics" + default y + +config SHELL_CMDS + bool "Enable built-in commands" + default y + help + Enable built-in commands like 'clear', 'history', etc. + +config SHELL_CMDS_RESIZE + bool "Enable resize command" + depends on SHELL_CMDS + default y + help + By default shell assumes width of a terminal screen set to 80 + characters. Each time terminal screen width is changed resize command + must be called to ensure correct text display on the terminal screen. + Resize command can be turned off to safe code memory (~0,5k). + +endif #SHELL +endmenu + diff --git a/subsys/shell/legacy_shell.c b/subsys/shell/legacy_shell.c new file mode 100644 index 000000000000..8fcd2f8bdc70 --- /dev/null +++ b/subsys/shell/legacy_shell.c @@ -0,0 +1,653 @@ +/* + * Copyright (c) 2015 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Console handler implementation of shell.h API + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include "mgmt/serial.h" + +#include + +#if defined(CONFIG_NATIVE_POSIX_CONSOLE) +#include "drivers/console/native_posix_console.h" +#endif + +#define ARGC_MAX 10 +#define COMMAND_MAX_LEN 50 +#define MODULE_NAME_MAX_LEN 20 +/* additional chars are " >" (include '\0' )*/ +#define PROMPT_SUFFIX 3 +#define PROMPT_MAX_LEN (MODULE_NAME_MAX_LEN + PROMPT_SUFFIX) + +/* command table is located in the dedicated memory segment (.shell_) */ +extern struct shell_module __shell_module_start[]; +extern struct shell_module __shell_module_end[]; + +extern struct shell_cmd __shell_cmd_start[]; +extern struct shell_cmd __shell_cmd_end[]; + +#define NUM_OF_SHELL_ENTITIES (__shell_module_end - __shell_module_start) +#define NUM_OF_SHELL_CMDS (__shell_cmd_end - __shell_cmd_start) + +static const char *prompt; +static char default_module_prompt[PROMPT_MAX_LEN]; +static struct shell_module *default_module; +static bool no_promt; + +#define STACKSIZE CONFIG_CONSOLE_SHELL_STACKSIZE +static K_THREAD_STACK_DEFINE(stack, STACKSIZE); +static struct k_thread shell_thread; + +#define MAX_CMD_QUEUED CONFIG_CONSOLE_SHELL_MAX_CMD_QUEUED +static struct console_input buf[MAX_CMD_QUEUED]; + +static struct k_fifo avail_queue; +static struct k_fifo cmds_queue; + +static shell_cmd_function_t app_cmd_handler; +static shell_prompt_function_t app_prompt_handler; + +static shell_mcumgr_function_t mcumgr_cmd_handler; +static void *mcumgr_arg; + +static const char *get_prompt(void) +{ + if (app_prompt_handler) { + const char *str; + + str = app_prompt_handler(); + if (str) { + return str; + } + } + + if (default_module) { + if (default_module->prompt) { + const char *ret; + + ret = default_module->prompt(); + if (ret) { + return ret; + } + } + + return default_module_prompt; + } + + return prompt; +} + +static void line_queue_init(void) +{ + int i; + + for (i = 0; i < MAX_CMD_QUEUED; i++) { + k_fifo_put(&avail_queue, &buf[i]); + } +} + +static size_t line2argv(char *str, char *argv[], size_t size) +{ + size_t argc = 0; + + if (!strlen(str)) { + return 0; + } + + while (*str && *str == ' ') { + str++; + } + + if (!*str) { + return 0; + } + + argv[argc++] = str; + + while ((str = strchr(str, ' '))) { + *str++ = '\0'; + + while (*str && *str == ' ') { + str++; + } + + if (!*str) { + break; + } + + argv[argc++] = str; + + if (argc == size) { + printk("Too many parameters (max %zu)\n", size - 1); + return 0; + } + } + + /* keep it POSIX style where argv[argc] is required to be NULL */ + argv[argc] = NULL; + + return argc; +} + +static struct shell_module *get_destination_module(const char *module_str) +{ + int i; + + for (i = 0; i < NUM_OF_SHELL_ENTITIES; i++) { + if (!strncmp(module_str, + __shell_module_start[i].module_name, + MODULE_NAME_MAX_LEN)) { + return &__shell_module_start[i]; + } + } + + return NULL; +} + +static int show_cmd_help(const struct shell_cmd *cmd, bool full) +{ + printk("Usage: %s %s\n", cmd->cmd_name, cmd->help ? cmd->help : ""); + + if (full && cmd->desc) { + printk("%s\n", cmd->desc); + } + + return 0; +} + +static void print_module_commands(struct shell_module *module) +{ + int i; + + printk("help\n"); + + for (i = 0; module->commands[i].cmd_name; i++) { + printk("%-28s %s\n", + module->commands[i].cmd_name, + module->commands[i].help ? + module->commands[i].help : ""); + } +} + +static const struct shell_cmd *get_cmd(const struct shell_cmd cmds[], + const char *cmd_str) +{ + int i; + + for (i = 0; cmds[i].cmd_name; i++) { + if (!strcmp(cmd_str, cmds[i].cmd_name)) { + return &cmds[i]; + } + } + + return NULL; +} + +static const struct shell_cmd *get_module_cmd(struct shell_module *module, + const char *cmd_str) +{ + return get_cmd(module->commands, cmd_str); +} + +static const struct shell_cmd *get_standalone(const char *command) +{ + int i; + + for (i = 0; i < NUM_OF_SHELL_CMDS; i++) { + if (!strcmp(command, __shell_cmd_start[i].cmd_name)) { + return &__shell_cmd_start[i]; + } + } + + return NULL; +} + +/** + * Handle internal 'help' command + */ +static int cmd_help(int argc, char *argv[]) +{ + struct shell_module *module = default_module; + + /* help per command */ + if (argc > 1) { + const struct shell_cmd *cmd; + const char *cmd_str; + + module = get_destination_module(argv[1]); + if (module) { + if (argc == 2) { + goto module_help; + } + + cmd_str = argv[2]; + } else { + cmd_str = argv[1]; + module = default_module; + } + + if (!module) { + cmd = get_standalone(cmd_str); + if (cmd) { + return show_cmd_help(cmd, true); + } + printk("No help found for '%s'\n", cmd_str); + return -EINVAL; + } + cmd = get_module_cmd(module, cmd_str); + if (cmd) { + return show_cmd_help(cmd, true); + } + printk("Unknown command '%s'\n", cmd_str); + return -EINVAL; + } + +module_help: + /* help per module */ + if (module) { + print_module_commands(module); + printk("\nEnter 'exit' to leave current module.\n"); + } else { /* help for all entities */ + int i; + + printk("[Modules]\n"); + + if (NUM_OF_SHELL_ENTITIES == 0) { + printk("No registered modules.\n"); + } + + for (i = 0; i < NUM_OF_SHELL_ENTITIES; i++) { + printk("%s\n", __shell_module_start[i].module_name); + } + + printk("\n[Commands]\n"); + + if (NUM_OF_SHELL_CMDS == 0) { + printk("No registered commands.\n"); + } + + for (i = 0; i < NUM_OF_SHELL_CMDS; i++) { + printk("%s\n", __shell_cmd_start[i].cmd_name); + } + + printk("\nTo select a module, enter 'select '.\n"); + } + + return 0; +} + +static int set_default_module(const char *name) +{ + struct shell_module *module; + + if (strlen(name) > MODULE_NAME_MAX_LEN) { + printk("Module name %s is too long, default is not changed\n", + name); + return -EINVAL; + } + + module = get_destination_module(name); + + if (!module) { + printk("Illegal module %s, default is not changed\n", name); + return -EINVAL; + } + + default_module = module; + + strncpy(default_module_prompt, name, MODULE_NAME_MAX_LEN); + strcat(default_module_prompt, "> "); + + return 0; +} + +static int cmd_select(int argc, char *argv[]) +{ + if (argc == 1) { + default_module = NULL; + return 0; + } + + return set_default_module(argv[1]); +} + +static int cmd_exit(int argc, char *argv[]) +{ + if (argc == 1) { + default_module = NULL; + } + + return 0; +} + +static int cmd_noprompt(int argc, char *argv[]) +{ + no_promt = true; + return 0; +} + +#define SHELL_CMD_NOPROMPT "noprompt" +SHELL_REGISTER_COMMAND(SHELL_CMD_NOPROMPT, cmd_noprompt, + "Disable shell prompt"); + +static const struct shell_cmd *get_internal(const char *command) +{ + static const struct shell_cmd internal_commands[] = { + { "help", cmd_help, "[command]" }, + { "select", cmd_select, "[module]" }, + { "exit", cmd_exit, NULL }, + { NULL }, + }; + + return get_cmd(internal_commands, command); +} + +void shell_register_mcumgr_handler(shell_mcumgr_function_t handler, void *arg) +{ + mcumgr_cmd_handler = handler; + mcumgr_arg = arg; +} + +int shell_exec(char *line) +{ + char *argv[ARGC_MAX + 1], **argv_start = argv; + const struct shell_cmd *cmd; + int argc, err; + + if (default_module && default_module->line2argv) { + argc = default_module->line2argv(line, argv, ARRAY_SIZE(argv)); + } else { + argc = line2argv(line, argv, ARRAY_SIZE(argv)); + } + if (!argc) { + return -EINVAL; + } + + cmd = get_internal(argv[0]); + if (cmd) { + goto done; + } + + cmd = get_standalone(argv[0]); + if (cmd) { + goto done; + } + + if (argc == 1 && !default_module && NUM_OF_SHELL_CMDS == 0) { + printk("No module selected. Use 'select' or 'help'.\n"); + return -EINVAL; + } + + if (default_module) { + cmd = get_module_cmd(default_module, argv[0]); + } + + if (!cmd && argc > 1) { + struct shell_module *module; + + module = get_destination_module(argv[0]); + if (module) { + cmd = get_module_cmd(module, argv[1]); + if (cmd) { + argc--; + argv_start++; + } + } + } + + if (!cmd) { + if (app_cmd_handler) { + return app_cmd_handler(argc, argv); + } + + printk("Unrecognized command: %s\n", argv[0]); + printk("Type 'help' for list of available commands\n"); + return -EINVAL; + } + +done: + err = cmd->cb(argc, argv_start); + if (err < 0) { + show_cmd_help(cmd, false); + } + + return err; +} + +static void shell(void *p1, void *p2, void *p3) +{ + bool skip_prompt = false; + + ARG_UNUSED(p1); + ARG_UNUSED(p2); + ARG_UNUSED(p3); + + printk("Zephyr Shell, Zephyr version: %s\n", KERNEL_VERSION_STRING); + printk("Type 'help' for a list of available commands\n"); + while (1) { + struct console_input *cmd; + + if (!no_promt && !skip_prompt) { + printk("%s", get_prompt()); +#if defined(CONFIG_NATIVE_POSIX_CONSOLE) + /* The native printk driver is line buffered */ + posix_flush_stdout(); +#endif + } + + cmd = k_fifo_get(&cmds_queue, K_FOREVER); + + /* If the received line is an mcumgr frame, divert it to the + * mcumgr handler. Don't print the shell prompt this time, as + * that will interfere with the mcumgr response. + */ + if (mcumgr_cmd_handler != NULL && cmd->is_mcumgr) { + mcumgr_cmd_handler(cmd->line, mcumgr_arg); + skip_prompt = true; + } else { + shell_exec(cmd->line); + skip_prompt = false; + } + + k_fifo_put(&avail_queue, cmd); + } +} + +static struct shell_module *get_completion_module(char *str, + char **command_prefix) +{ + char dest_str[MODULE_NAME_MAX_LEN]; + struct shell_module *dest; + char *start; + + /* remove ' ' at the beginning of the line */ + while (*str && *str == ' ') { + str++; + } + + if (!*str) { + return NULL; + } + + start = str; + + if (default_module) { + dest = default_module; + /* caller function already checks str len and put '\0' */ + *command_prefix = str; + } else { + dest = NULL; + } + + /* + * In case of a default module: only one parameter is possible. + * Otherwise, only two parameters are possibles. + */ + str = strchr(str, ' '); + if (default_module) { + return str ? NULL : dest; + } + + if (!str) { + return NULL; + } + + if ((str - start + 1) >= MODULE_NAME_MAX_LEN) { + return NULL; + } + + strncpy(dest_str, start, (str - start + 1)); + dest_str[str - start] = '\0'; + dest = get_destination_module(dest_str); + if (!dest) { + return NULL; + } + + str++; + + /* caller func has already checked str len and put '\0' at the end */ + *command_prefix = str; + str = strchr(str, ' '); + + /* only two parameters are possibles in case of no default module */ + return str ? NULL : dest; +} + +static u8_t completion(char *line, u8_t len) +{ + const char *first_match = NULL; + int common_chars = -1, space = 0; + int i, command_len; + const struct shell_module *module; + char *command_prefix; + + if (len >= (MODULE_NAME_MAX_LEN + COMMAND_MAX_LEN - 1)) { + return 0; + } + + /* + * line to completion is not ended by '\0' as the line that gets from + * k_fifo_get function + */ + line[len] = '\0'; + module = get_completion_module(line, &command_prefix); + if (!module) { + return 0; + } + + command_len = strlen(command_prefix); + + for (i = 0; module->commands[i].cmd_name; i++) { + int j; + + if (strncmp(command_prefix, + module->commands[i].cmd_name, command_len)) { + continue; + } + + if (!first_match) { + first_match = module->commands[i].cmd_name; + continue; + } + + /* more commands match, print first match */ + if (first_match && (common_chars < 0)) { + printk("\n%s\n", first_match); + common_chars = strlen(first_match); + } + + /* cut common part of matching names */ + for (j = 0; j < common_chars; j++) { + if (first_match[j] != module->commands[i].cmd_name[j]) { + break; + } + } + + common_chars = j; + + printk("%s\n", module->commands[i].cmd_name); + } + + /* no match, do nothing */ + if (!first_match) { + return 0; + } + + if (common_chars >= 0) { + /* multiple match, restore prompt */ + printk("%s", get_prompt()); + printk("%s", line); + } else { + common_chars = strlen(first_match); + space = 1; + } + + /* complete common part */ + for (i = command_len; i < common_chars; i++) { + printk("%c", first_match[i]); + line[len++] = first_match[i]; + } + + /* for convenience add space after command */ + if (space) { + printk(" "); + line[len] = ' '; + } + + return common_chars - command_len + space; +} + + +void shell_init(const char *str) +{ + k_fifo_init(&cmds_queue); + k_fifo_init(&avail_queue); + + line_queue_init(); + + prompt = str ? str : ""; + + k_thread_create(&shell_thread, stack, STACKSIZE, shell, NULL, NULL, + NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); + + /* Register console handler */ + console_register_line_input(&avail_queue, &cmds_queue, completion); +} + +/** @brief Optionally register an app default cmd handler. + * + * @param handler To be called if no cmd found in cmds registered with + * shell_init. + */ +void shell_register_app_cmd_handler(shell_cmd_function_t handler) +{ + app_cmd_handler = handler; +} + +void shell_register_prompt_handler(shell_prompt_function_t handler) +{ + app_prompt_handler = handler; +} + +void shell_register_default_module(const char *name) +{ + int err = set_default_module(name); + + if (!err) { + printk("\n%s", default_module_prompt); + } +} diff --git a/subsys/shell/modules/kernel_service.c b/subsys/shell/modules/kernel_service.c index 52a5c47a9b0a..876188c0a50d 100644 --- a/subsys/shell/modules/kernel_service.c +++ b/subsys/shell/modules/kernel_service.c @@ -5,7 +5,7 @@ */ #include -#include +#include #include #include #include diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c index 32c95843868a..359922af5db7 100644 --- a/subsys/shell/shell.c +++ b/subsys/shell/shell.c @@ -1,656 +1,1689 @@ /* - * Copyright (c) 2015 Intel Corporation + * Copyright (c) 2018 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ -/** - * @file - * @brief Console handler implementation of shell.h API - */ +#include +#include +#include +#include +#include "shell_utils.h" +#include "shell_ops.h" +#include "shell_wildcard.h" +#include "shell_vt100.h" +#include +#include + +/* 2 == 1 char for cmd + 1 char for '\0' */ +#if (CONFIG_SHELL_CMD_BUFF_SIZE < 2) + #error too small CONFIG_SHELL_CMD_BUFF_SIZE +#endif -#include -#include -#include -#include +#if (CONFIG_SHELL_PRINTF_BUFF_SIZE < 1) + #error too small SHELL_PRINTF_BUFF_SIZE +#endif -#include -#include -#include -#include -#include "mgmt/serial.h" +#define SHELL_MSG_COMMAND_NOT_FOUND ": command not found" +#define SHELL_MSG_TAB_OVERFLOWED \ + "Tab function: commands counter overflowed.\r\n" -#include +#define SHELL_INIT_OPTION_PRINTER (NULL) -#if defined(CONFIG_NATIVE_POSIX_CONSOLE) -#include "drivers/console/native_posix_console.h" -#endif +/* Initial cursor position is: (1, 1). */ +#define SHELL_INITIAL_CURS_POS (1u) -#define ARGC_MAX 10 -#define COMMAND_MAX_LEN 50 -#define MODULE_NAME_MAX_LEN 20 -/* additional chars are " >" (include '\0' )*/ -#define PROMPT_SUFFIX 3 -#define PROMPT_MAX_LEN (MODULE_NAME_MAX_LEN + PROMPT_SUFFIX) +static void shell_execute(const struct shell *shell); -/* command table is located in the dedicated memory segment (.shell_) */ -extern struct shell_module __shell_module_start[]; -extern struct shell_module __shell_module_end[]; +extern const struct shell_cmd_entry __shell_root_cmds_start[0]; +extern const struct shell_cmd_entry __shell_root_cmds_end[0]; -extern struct shell_cmd __shell_cmd_start[]; -extern struct shell_cmd __shell_cmd_end[]; +static inline const struct shell_cmd_entry *shell_root_cmd_get(u32_t id) +{ + return &__shell_root_cmds_start[id]; +} -#define NUM_OF_SHELL_ENTITIES (__shell_module_end - __shell_module_start) -#define NUM_OF_SHELL_CMDS (__shell_cmd_end - __shell_cmd_start) +static inline u32_t shell_root_cmd_count(void) +{ + return ((void *)__shell_root_cmds_end - + (void *)__shell_root_cmds_start)/ + sizeof(struct shell_cmd_entry); +} -static const char *prompt; -static char default_module_prompt[PROMPT_MAX_LEN]; -static struct shell_module *default_module; -static bool no_promt; +static inline void transport_buffer_flush(const struct shell *shell) +{ + shell_fprintf_buffer_flush(shell->fprintf_ctx); +} -#define STACKSIZE CONFIG_CONSOLE_SHELL_STACKSIZE -static K_THREAD_STACK_DEFINE(stack, STACKSIZE); -static struct k_thread shell_thread; +static inline void help_flag_set(const struct shell *shell) +{ + shell->ctx->internal.flags.show_help = 1; +} +static inline void help_flag_clear(const struct shell *shell) +{ + shell->ctx->internal.flags.show_help = 0; +} -#define MAX_CMD_QUEUED CONFIG_CONSOLE_SHELL_MAX_CMD_QUEUED -static struct console_input buf[MAX_CMD_QUEUED]; +/* Function returns true if delete escape code shall be interpreted as + * backspace. + */ +static inline bool flag_delete_mode_set(const struct shell *shell) +{ + return shell->ctx->internal.flags.mode_delete == 1 ? true : false; +} -static struct k_fifo avail_queue; -static struct k_fifo cmds_queue; +static inline bool flag_processing_is_set(const struct shell *shell) +{ + return shell->ctx->internal.flags.processing == 1 ? true : false; +} -static shell_cmd_function_t app_cmd_handler; -static shell_prompt_function_t app_prompt_handler; +static inline void receive_state_change(const struct shell *shell, + enum shell_receive_state state) +{ + shell->ctx->receive_state = state; +} -static shell_mcumgr_function_t mcumgr_cmd_handler; -static void *mcumgr_arg; +static void shell_cmd_buffer_clear(const struct shell *shell) +{ + shell->ctx->cmd_buff[0] = '\0'; /* clear command buffer */ + shell->ctx->cmd_buff_pos = 0; + shell->ctx->cmd_buff_len = 0; +} -static const char *get_prompt(void) +/* Function sends data stream to the shell instance. Each time before the + * shell_write function is called, it must be ensured that IO buffer of fprintf + * is flushed to avoid synchronization issues. + * For that purpose, use function transport_buffer_flush(shell) + */ +static void shell_write(const struct shell *shell, const void *data, + size_t length) { - if (app_prompt_handler) { - const char *str; + assert(shell && data); + + size_t offset = 0; + size_t tmp_cnt; + + while (length) { + int err = shell->iface->api->write(shell->iface, + &((const u8_t *) data)[offset], length, + &tmp_cnt); + (void)err; + assert(err == 0); + assert(length >= tmp_cnt); + offset += tmp_cnt; + length -= tmp_cnt; + if (tmp_cnt == 0 && + (shell->ctx->state != SHELL_STATE_PANIC_MODE_ACTIVE)) { + /* todo semaphore pend*/ + if (IS_ENABLED(CONFIG_MULTITHREADING)) { + k_poll(&shell->ctx->events[SHELL_SIGNAL_TXDONE], + 1, K_FOREVER); + } else { + /* Blocking wait in case of bare metal. */ + while (!shell->ctx->internal.flags.tx_rdy) { - str = app_prompt_handler(); - if (str) { - return str; + } + shell->ctx->internal.flags.tx_rdy = 0; + } } } +} - if (default_module) { - if (default_module->prompt) { - const char *ret; +/* @brief Function shall be used to search commands. + * + * It moves the pointer entry to command of static command structure. If the + * command cannot be found, the function will set entry to NULL. + * + * @param command Pointer to command which will be processed (no matter + * the root command). + * @param lvl Level of the requested command. + * @param idx Index of the requested command. + * @param entry Pointer which points to subcommand[idx] after function + * execution. + * @param st_entry Pointer to the structure where dynamic entry data can be + * stored. + */ +static void cmd_get(const struct shell_cmd_entry *command, size_t lvl, + size_t idx, const struct shell_static_entry **entry, + struct shell_static_entry *d_entry) +{ + assert(entry != NULL); + assert(command != NULL); + assert(d_entry != NULL); - ret = default_module->prompt(); - if (ret) { - return ret; - } + if (lvl == SHELL_CMD_ROOT_LVL) { + if (idx < shell_root_cmd_count()) { + const struct shell_cmd_entry *cmd; + + cmd = shell_root_cmd_get(idx); + *entry = cmd->u.entry; + } else { + *entry = NULL; } + return; + } - return default_module_prompt; + if (command == NULL) { + *entry = NULL; + return; } - return prompt; + if (command->is_dynamic) { + command->u.dynamic_get(idx, d_entry); + *entry = (d_entry->syntax != NULL) ? d_entry : NULL; + } else { + *entry = (command->u.entry[idx].syntax != NULL) ? + &command->u.entry[idx] : NULL; + } } -static void line_queue_init(void) +static void vt100_color_set(const struct shell *shell, + enum shell_vt100_color color) { - int i; - for (i = 0; i < MAX_CMD_QUEUED; i++) { - k_fifo_put(&avail_queue, &buf[i]); + if (shell->ctx->vt100_ctx.col.col == color) { + return; } -} -static size_t line2argv(char *str, char *argv[], size_t size) -{ - size_t argc = 0; + shell->ctx->vt100_ctx.col.col = color; - if (!strlen(str)) { - return 0; - } + if (color != SHELL_NORMAL) { - while (*str && *str == ' ') { - str++; + u8_t cmd[] = SHELL_VT100_COLOR(color - 1); + + shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd); + } else { + static const u8_t cmd[] = SHELL_VT100_MODESOFF; + + shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd); } +} - if (!*str) { - return 0; +static void vt100_bgcolor_set(const struct shell *shell, + enum shell_vt100_color bgcolor) +{ + if ((bgcolor == SHELL_NORMAL) || + (shell->ctx->vt100_ctx.col.bgcol == bgcolor)) { + return; } - argv[argc++] = str; + /* -1 because default value is first in enum */ + u8_t cmd[] = SHELL_VT100_BGCOLOR(bgcolor - 1); - while ((str = strchr(str, ' '))) { - *str++ = '\0'; + shell->ctx->vt100_ctx.col.bgcol = bgcolor; + shell_raw_fprintf(shell->fprintf_ctx, "%s", cmd); - while (*str && *str == ' ') { - str++; - } +} - if (!*str) { - break; - } +static inline void vt100_colors_store(const struct shell *shell, + struct shell_vt100_colors *color) +{ + memcpy(color, &shell->ctx->vt100_ctx.col, sizeof(*color)); +} - argv[argc++] = str; +static void vt100_colors_restore(const struct shell *shell, + const struct shell_vt100_colors *color) +{ + vt100_color_set(shell, color->col); + vt100_bgcolor_set(shell, color->bgcol); +} - if (argc == size) { - printk("Too many parameters (max %zu)\n", size - 1); - return 0; - } +static void shell_state_set(const struct shell *shell, enum shell_state state) +{ + shell->ctx->state = state; + + if (state == SHELL_STATE_ACTIVE) { + shell_cmd_buffer_clear(shell); + shell_fprintf(shell, SHELL_INFO, "%s", shell->name); + } +} + +static void tab_item_print(const struct shell *shell, const char *option, + u16_t longest_option) +{ + static const char *tab = " "; + u16_t columns; + u16_t diff; + + /* Function initialization has been requested. */ + if (option == NULL) { + shell->ctx->vt100_ctx.printed_cmd = 0; + return; } - /* keep it POSIX style where argv[argc] is required to be NULL */ - argv[argc] = NULL; + longest_option += shell_strlen(tab); + + columns = (shell->ctx->vt100_ctx.cons.terminal_wid + - shell_strlen(tab)) / longest_option; + diff = longest_option - shell_strlen(option); + + if (shell->ctx->vt100_ctx.printed_cmd++ % columns == 0) { + shell_fprintf(shell, SHELL_OPTION, "\r\n%s%s", tab, option); + } else { + shell_fprintf(shell, SHELL_OPTION, "%s", option); + } - return argc; + shell_op_cursor_horiz_move(shell, diff); } -static struct shell_module *get_destination_module(const char *module_str) +static void history_init(const struct shell *shell) { - int i; + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + return; + } - for (i = 0; i < NUM_OF_SHELL_ENTITIES; i++) { - if (!strncmp(module_str, - __shell_module_start[i].module_name, - MODULE_NAME_MAX_LEN)) { - return &__shell_module_start[i]; - } + shell_history_init(shell->history); +} + +static void history_purge(const struct shell *shell) +{ + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + return; } - return NULL; + shell_history_purge(shell->history); } -static int show_cmd_help(const struct shell_cmd *cmd, bool full) +static void history_mode_exit(const struct shell *shell) { - printk("Usage: %s %s\n", cmd->cmd_name, cmd->help ? cmd->help : ""); + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + return; + } + + shell_history_mode_exit(shell->history); +} - if (full && cmd->desc) { - printk("%s\n", cmd->desc); +static void history_put(const struct shell *shell, u8_t *line, size_t length) +{ + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + return; } - return 0; + shell_history_put(shell->history, line, length); } -static void print_module_commands(struct shell_module *module) +static void history_handle(const struct shell *shell, bool up) { - int i; + bool history_mode; + size_t len; - printk("help\n"); + /*optional feature */ + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + return; + } - for (i = 0; module->commands[i].cmd_name; i++) { - printk("%-28s %s\n", - module->commands[i].cmd_name, - module->commands[i].help ? - module->commands[i].help : ""); + /* Backup command if history is entered */ + if (!shell_history_active(shell->history)) { + if (up) { + u16_t cmd_len = shell_strlen(shell->ctx->cmd_buff); + + if (cmd_len) { + strcpy(shell->ctx->temp_buff, + shell->ctx->cmd_buff); + } else { + shell->ctx->temp_buff[0] = '\0'; + } + } else { + /* Pressing 'down' not in history mode has no effect. */ + return; + } + } + + /* Start by checking if history is not empty. */ + history_mode = shell_history_get(shell->history, true, + shell->ctx->cmd_buff, &len); + + /* On exiting history mode print backed up command. */ + if (!history_mode) { + strcpy(shell->ctx->cmd_buff, shell->ctx->temp_buff); + len = shell_strlen(shell->ctx->cmd_buff); + } + + if (len) { + shell_op_cursor_home_move(shell); + clear_eos(shell); + shell_fprintf(shell, SHELL_NORMAL, "%s", shell->ctx->cmd_buff); + shell->ctx->cmd_buff_pos = len; + shell->ctx->cmd_buff_len = len; + shell_op_cond_next_line(shell); } } -static const struct shell_cmd *get_cmd(const struct shell_cmd cmds[], - const char *cmd_str) +static const struct shell_static_entry *find_cmd( + const struct shell_cmd_entry *cmd, + size_t lvl, + char *cmd_str, + struct shell_static_entry *d_entry) { - int i; + const struct shell_static_entry *entry = NULL; + size_t idx = 0; - for (i = 0; cmds[i].cmd_name; i++) { - if (!strcmp(cmd_str, cmds[i].cmd_name)) { - return &cmds[i]; + do { + cmd_get(cmd, lvl, idx++, &entry, d_entry); + if (entry && (strcmp(cmd_str, entry->syntax) == 0)) { + return entry; + } + } while (entry); + + return entry; +} + +/** @brief Function for getting last valid command in list of arguments. */ +static const struct shell_static_entry *get_last_command( + const struct shell *shell, + size_t argc, + char *argv[], + size_t *match_arg, + struct shell_static_entry *d_entry) +{ + const struct shell_static_entry *prev_entry = NULL; + const struct shell_cmd_entry *prev_cmd = NULL; + const struct shell_static_entry *entry = NULL; + *match_arg = SHELL_CMD_ROOT_LVL; + + while (*match_arg < argc) { + + if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) { + /* ignore wildcard argument */ + if (shell_wildcard_character_exist(argv[*match_arg])) { + (*match_arg)++; + continue; + } + } + + entry = find_cmd(prev_cmd, *match_arg, argv[*match_arg], + d_entry); + if (entry) { + prev_cmd = entry->subcmd; + prev_entry = entry; + (*match_arg)++; + } else { + entry = NULL; + break; } } - return NULL; + return entry; } -static const struct shell_cmd *get_module_cmd(struct shell_module *module, - const char *cmd_str) +static inline u16_t completion_space_get(const struct shell *shell) { - return get_cmd(module->commands, cmd_str); + u16_t space = (CONFIG_SHELL_CMD_BUFF_SIZE - 1) - + shell->ctx->cmd_buff_len; + return space; } -static const struct shell_cmd *get_standalone(const char *command) +/* Prepare arguments and return number of space available for completion. */ +static bool shell_tab_prepare(const struct shell *shell, + const struct shell_static_entry **cmd, + char **argv, size_t *argc, + size_t *complete_arg_idx, + struct shell_static_entry *d_entry) { - int i; + u16_t compl_space = completion_space_get(shell); + size_t search_argc; - for (i = 0; i < NUM_OF_SHELL_CMDS; i++) { - if (!strcmp(command, __shell_cmd_start[i].cmd_name)) { - return &__shell_cmd_start[i]; - } + if (compl_space == 0) { + return false; } - return NULL; + /* If the Tab key is pressed, "history mode" must be terminated because + * tab and history handlers are sharing the same array: temp_buff. + */ + history_mode_exit(shell); + + /* Copy command from its beginning to cursor position. */ + memcpy(shell->ctx->temp_buff, shell->ctx->cmd_buff, + shell->ctx->cmd_buff_pos); + shell->ctx->temp_buff[shell->ctx->cmd_buff_pos] = '\0'; + + /* Create argument list. */ + (void)shell_make_argv(argc, argv, shell->ctx->temp_buff, + CONFIG_SHELL_ARGC_MAX); + + /* If last command is not completed (followed by space) it is treated + * as uncompleted one. + */ + int space = isspace((int)shell->ctx->cmd_buff[ + shell->ctx->cmd_buff_pos - 1]); + + /* root command completion */ + if ((*argc == 0) || ((space == 0) && (*argc == 1))) { + *complete_arg_idx = SHELL_CMD_ROOT_LVL; + *cmd = NULL; + return true; + } + + search_argc = space ? *argc : *argc - 1; + + *cmd = get_last_command(shell, search_argc, argv, complete_arg_idx, + d_entry); + + /* if search_argc == 0 (empty command line) get_last_command will return + * NULL tab is allowed, otherwise not. + */ + if ((*cmd == NULL) && (search_argc != 0)) { + return false; + } + + return true; } -/** - * Handle internal 'help' command - */ -static int cmd_help(int argc, char *argv[]) +static inline bool is_completion_candidate(const char *candidate, + const char *str, size_t len) { - struct shell_module *module = default_module; + return (strncmp(candidate, str, len) == 0) ? true : false; +} - /* help per command */ - if (argc > 1) { - const struct shell_cmd *cmd; - const char *cmd_str; +static void find_completion_candidates(const struct shell_static_entry *cmd, + const char *incompl_cmd, + size_t *first_idx, size_t *cnt, + u16_t *longest) +{ + size_t incompl_cmd_len = shell_strlen(incompl_cmd); + const struct shell_static_entry *candidate; + struct shell_static_entry dynamic_entry; + bool found = false; + size_t idx = 0; - module = get_destination_module(argv[1]); - if (module) { - if (argc == 2) { - goto module_help; - } + *longest = 0; + *cnt = 0; - cmd_str = argv[2]; - } else { - cmd_str = argv[1]; - module = default_module; + while (true) { + cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, + idx, &candidate, &dynamic_entry); + + if (!candidate) { + break; } - if (!module) { - cmd = get_standalone(cmd_str); - if (cmd) { - return show_cmd_help(cmd, true); - } else { - printk("No help found for '%s'\n", cmd_str); - return -EINVAL; + if (is_completion_candidate(candidate->syntax, incompl_cmd, + incompl_cmd_len)) { + size_t slen = strlen(candidate->syntax); + + *longest = (slen > *longest) ? slen : *longest; + (*cnt)++; + + if (!found) { + *first_idx = idx; } + + found = true; } else { - cmd = get_module_cmd(module, cmd_str); - if (cmd) { - return show_cmd_help(cmd, true); - } else { - printk("Unknown command '%s'\n", cmd_str); - return -EINVAL; + if (found) { + break; } } + idx++; } +} -module_help: - /* help per module */ - if (module) { - print_module_commands(module); - printk("\nEnter 'exit' to leave current module.\n"); - } else { /* help for all entities */ - int i; +static void autocomplete(const struct shell *shell, + const struct shell_static_entry *cmd, + const char *arg, + size_t subcmd_idx) +{ + const struct shell_static_entry *match; + size_t arg_len = shell_strlen(arg); + size_t cmd_len; - printk("[Modules]\n"); + /* shell->ctx->active_cmd can be safely used outside of command context + * to save stack + */ + cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, + subcmd_idx, &match, &shell->ctx->active_cmd); + cmd_len = shell_strlen(match->syntax); + + /* no exact match found */ + if (cmd_len != arg_len) { + shell_op_completion_insert(shell, + match->syntax + arg_len, + cmd_len - arg_len); + } - if (NUM_OF_SHELL_ENTITIES == 0) { - printk("No registered modules.\n"); + /* Next character in the buffer is not 'space'. */ + if (!isspace((int) shell->ctx->cmd_buff[ + shell->ctx->cmd_buff_pos])) { + if (shell->ctx->internal.flags.insert_mode) { + shell->ctx->internal.flags.insert_mode = 0; + shell_op_char_insert(shell, ' '); + shell->ctx->internal.flags.insert_mode = 1; + } else { + shell_op_char_insert(shell, ' '); } + } else { + /* case: + * | | -> cursor + * cons_name $: valid_cmd valid_sub_cmd| |argument + */ + shell_op_cursor_move(shell, 1); + /* result: + * cons_name $: valid_cmd valid_sub_cmd |a|rgument + */ + } +} - for (i = 0; i < NUM_OF_SHELL_ENTITIES; i++) { - printk("%s\n", __shell_module_start[i].module_name); - } +static size_t shell_str_common(const char *s1, const char *s2, size_t n) +{ + size_t common = 0; - printk("\n[Commands]\n"); + while ((n > 0) && (*s1 == *s2) && (*s1 != '\0')) { + s1++; + s2++; + n--; + common++; + } - if (NUM_OF_SHELL_CMDS == 0) { - printk("No registered commands.\n"); - } + return common; +} - for (i = 0; i < NUM_OF_SHELL_CMDS; i++) { - printk("%s\n", __shell_cmd_start[i].cmd_name); - } +static void tab_options_print(const struct shell *shell, + const struct shell_static_entry *cmd, + size_t first, size_t cnt, u16_t longest) +{ + const struct shell_static_entry *match; + size_t idx = first; + + /* Printing all matching commands (options). */ + tab_item_print(shell, SHELL_INIT_OPTION_PRINTER, longest); - printk("\nTo select a module, enter 'select '.\n"); + while (cnt) { + /* shell->ctx->active_cmd can be safely used outside of command + * context to save stack + */ + cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, + idx, &match, &shell->ctx->active_cmd); + tab_item_print(shell, match->syntax, longest); + cnt--; + idx++; } - return 0; + shell_fprintf(shell, SHELL_INFO, "\r\n%s", shell->name); + shell_fprintf(shell, SHELL_NORMAL, "%s", shell->ctx->cmd_buff); + + shell_op_cursor_position_synchronize(shell); } -static int set_default_module(const char *name) +static u16_t common_beginning_find(const struct shell_static_entry *cmd, + const char **str, + size_t first, size_t cnt) { - struct shell_module *module; + struct shell_static_entry dynamic_entry; + const struct shell_static_entry *match; + u16_t common = UINT16_MAX; - if (strlen(name) > MODULE_NAME_MAX_LEN) { - printk("Module name %s is too long, default is not changed\n", - name); - return -EINVAL; - } - module = get_destination_module(name); + cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, + first, &match, &dynamic_entry); - if (!module) { - printk("Illegal module %s, default is not changed\n", name); - return -EINVAL; - } + *str = match->syntax; - default_module = module; + for (size_t idx = first + 1; idx < first + cnt; idx++) { + struct shell_static_entry dynamic_entry2; + const struct shell_static_entry *match2; + int curr_common; - strncpy(default_module_prompt, name, MODULE_NAME_MAX_LEN); - strcat(default_module_prompt, "> "); + cmd_get(cmd ? cmd->subcmd : NULL, cmd ? 1 : 0, + idx, &match2, &dynamic_entry2); - return 0; + curr_common = shell_str_common(match->syntax, match2->syntax, + UINT16_MAX); + common = (curr_common < common) ? curr_common : common; + } + + return common; } -static int cmd_select(int argc, char *argv[]) +static void partial_autocomplete(const struct shell *shell, + const struct shell_static_entry *cmd, + const char *arg, + size_t first, size_t cnt) { - if (argc == 1) { - default_module = NULL; - return 0; - } + const char *completion; + u16_t common = common_beginning_find(cmd, &completion, first, cnt); + int arg_len = shell_strlen(arg); - return set_default_module(argv[1]); + if (common) { + shell_op_completion_insert(shell, &completion[arg_len], + common - arg_len); + } } -static int cmd_exit(int argc, char *argv[]) +static void shell_tab_handle(const struct shell *shell) { - if (argc == 1) { - default_module = NULL; + /* +1 reserved for NULL in function shell_make_argv */ + char *argv[CONFIG_SHELL_ARGC_MAX + 1]; + /* d_entry - placeholder for dynamic command */ + struct shell_static_entry d_entry; + const struct shell_static_entry *cmd; + size_t arg_idx; + u16_t longest; + size_t first; + size_t argc; + size_t cnt; + + + bool tab_possible = shell_tab_prepare(shell, &cmd, argv, &argc, + &arg_idx, &d_entry); + + if (tab_possible == false) { + return; } - return 0; + find_completion_candidates(cmd, argv[arg_idx], &first, &cnt, &longest); + + if (!cnt) { + /* No candidates to propose. */ + return; + } else if (cnt == 1) { + /* Autocompletion.*/ + autocomplete(shell, cmd, argv[arg_idx], first); + } else { + tab_options_print(shell, cmd, first, cnt, longest); + partial_autocomplete(shell, cmd, argv[arg_idx], first, cnt); + } } -static int cmd_noprompt(int argc, char *argv[]) +#define SHELL_ASCII_MAX_CHAR (127u) +static inline int ascii_filter(const char data) { - no_promt = true; - return 0; + return (u8_t) data > SHELL_ASCII_MAX_CHAR ? + -EINVAL : 0; } -#define SHELL_CMD_NOPROMPT "noprompt" -SHELL_REGISTER_COMMAND(SHELL_CMD_NOPROMPT, cmd_noprompt, - "Disable shell prompt"); +static void metakeys_handle(const struct shell *shell, char data) +{ + /* Optional feature */ + if (!IS_ENABLED(CONFIG_SHELL_METAKEYS)) { + return; + } + + switch (data) { + case SHELL_VT100_ASCII_CTRL_A: /* CTRL + A */ + shell_op_cursor_home_move(shell); + break; + + case SHELL_VT100_ASCII_CTRL_C: /* CTRL + C */ + shell_op_cursor_end_move(shell); + if (!shell_cursor_in_empty_line(shell)) { + cursor_next_line_move(shell); + } + shell_state_set(shell, SHELL_STATE_ACTIVE); + break; + + case SHELL_VT100_ASCII_CTRL_E: /* CTRL + E */ + shell_op_cursor_end_move(shell); + break; + + case SHELL_VT100_ASCII_CTRL_L: /* CTRL + L */ + SHELL_VT100_CMD(shell, SHELL_VT100_CURSORHOME); + SHELL_VT100_CMD(shell, SHELL_VT100_CLEARSCREEN); + shell_fprintf(shell, SHELL_INFO, "%s", shell->name); + if (flag_echo_is_set(shell)) { + shell_fprintf(shell, SHELL_NORMAL, "%s", + shell->ctx->cmd_buff); + shell_op_cursor_position_synchronize(shell); + } + break; + + case SHELL_VT100_ASCII_CTRL_U: /* CTRL + U */ + shell_op_cursor_home_move(shell); + shell_cmd_buffer_clear(shell); + clear_eos(shell); + break; -static const struct shell_cmd *get_internal(const char *command) + case SHELL_VT100_ASCII_CTRL_W: /* CTRL + W */ + shell_op_word_remove(shell); + break; + + default: + break; + } +} + +static void shell_state_collect(const struct shell *shell) { - static const struct shell_cmd internal_commands[] = { - { "help", cmd_help, "[command]" }, - { "select", cmd_select, "[module]" }, - { "exit", cmd_exit, NULL }, - { NULL }, - }; + size_t count = 0; + char data; + + while (true) { + (void)shell->iface->api->read(shell->iface, &data, + sizeof(data), &count); + if (count == 0) { + return; + } + + if (ascii_filter(data) != 0) { + continue; + } + + /* todo pwr_mgmt_feed();*/ + + switch (shell->ctx->receive_state) { + case SHELL_RECEIVE_DEFAULT: + if (data == shell->newline_char) { + if (!shell->ctx->cmd_buff_len) { + history_mode_exit(shell); + cursor_next_line_move(shell); + } else { + /* Command execution */ + shell_execute(shell); + } + shell_state_set(shell, SHELL_STATE_ACTIVE); + return; + } + switch (data) { + case SHELL_VT100_ASCII_ESC: /* ESCAPE */ + receive_state_change(shell, SHELL_RECEIVE_ESC); + break; + + case '\0': + break; + + case '\t': /* TAB */ + if (flag_echo_is_set(shell)) { + shell_tab_handle(shell); + } + break; + + case SHELL_VT100_ASCII_BSPACE: /* BACKSPACE */ + if (flag_echo_is_set(shell)) { + shell_op_char_backspace(shell); + } + break; + + case SHELL_VT100_ASCII_DEL: /* DELETE */ + if (flag_echo_is_set(shell)) { + if (flag_delete_mode_set(shell)) { + shell_op_char_backspace(shell); + + } else { + shell_op_char_delete(shell); + } + } + break; + + default: + if (isprint((int) data)) { + shell_op_char_insert(shell, data); + } else { + metakeys_handle(shell, data); + } + break; + } + break; + + case SHELL_RECEIVE_ESC: + if (data == '[') { + receive_state_change(shell, + SHELL_RECEIVE_ESC_SEQ); + } else { + receive_state_change(shell, + SHELL_RECEIVE_DEFAULT); + } + break; + + case SHELL_RECEIVE_ESC_SEQ: + receive_state_change(shell, SHELL_RECEIVE_DEFAULT); + + if (!flag_echo_is_set(shell)) { + return; + } + + switch (data) { + case 'A': /* UP arrow */ + history_handle(shell, true); + break; + + case 'B': /* DOWN arrow */ + history_handle(shell, false); + break; + + case 'C': /* RIGHT arrow */ + shell_op_right_arrow(shell); + break; + + case 'D': /* LEFT arrow */ + shell_op_left_arrow(shell); + break; + + case '4': /* END Button in ESC[n~ mode */ + receive_state_change(shell, + SHELL_RECEIVE_TILDE_EXP); + /* fall through */ + /* no break */ + case 'F': /* END Button in VT100 mode */ + shell_op_cursor_end_move(shell); + break; + + case '1': /* HOME Button in ESC[n~ mode */ + receive_state_change(shell, + SHELL_RECEIVE_TILDE_EXP); + /* fall through */ + /* no break */ + case 'H': /* HOME Button in VT100 mode */ + shell_op_cursor_home_move(shell); + break; + + case '2': /* INSERT Button in ESC[n~ mode */ + receive_state_change(shell, + SHELL_RECEIVE_TILDE_EXP); + /* fall through */ + /* no break */ + case 'L': /* INSERT Button in VT100 mode */ + shell->ctx->internal.flags.insert_mode ^= 1; + break; + + case '3':/* DELETE Button in ESC[n~ mode */ + receive_state_change(shell, + SHELL_RECEIVE_TILDE_EXP); + if (flag_echo_is_set(shell)) { + shell_op_char_delete(shell); + } + break; + + default: + break; + } + break; + + case SHELL_RECEIVE_TILDE_EXP: + receive_state_change(shell, SHELL_RECEIVE_DEFAULT); + break; - return get_cmd(internal_commands, command); + default: + receive_state_change(shell, SHELL_RECEIVE_DEFAULT); + break; + } + } } -void shell_register_mcumgr_handler(shell_mcumgr_function_t handler, void *arg) +static void cmd_trim(const struct shell *shell) { - mcumgr_cmd_handler = handler; - mcumgr_arg = arg; + shell_buffer_trim(shell->ctx->cmd_buff, &shell->ctx->cmd_buff_len); + shell->ctx->cmd_buff_pos = shell->ctx->cmd_buff_len; } -int shell_exec(char *line) +/* Function returning pointer to root command matching requested syntax. */ +static const struct shell_cmd_entry *root_cmd_find(const char *syntax) { - char *argv[ARGC_MAX + 1], **argv_start = argv; - const struct shell_cmd *cmd; - int argc, err; + const size_t cmd_count = shell_root_cmd_count(); + const struct shell_cmd_entry *cmd; - if (default_module && default_module->line2argv) { - argc = default_module->line2argv(line, argv, ARRAY_SIZE(argv)); - } else { - argc = line2argv(line, argv, ARRAY_SIZE(argv)); + for (size_t cmd_idx = 0; cmd_idx < cmd_count; ++cmd_idx) { + cmd = shell_root_cmd_get(cmd_idx); + if (strcmp(syntax, cmd->u.entry->syntax) == 0) { + return cmd; + } } - if (!argc) { - return -EINVAL; + + return NULL; +} + +/* Function is analyzing the command buffer to find matching commands. Next, it + * invokes the last recognized command which has a handler and passes the rest + * of command buffer as arguments. + */ +static void shell_execute(const struct shell *shell) +{ + struct shell_static_entry d_entry; /* Memory for dynamic commands. */ + char *argv[CONFIG_SHELL_ARGC_MAX + 1]; /* +1 reserved for NULL */ + const struct shell_static_entry *p_static_entry = NULL; + const struct shell_cmd_entry *p_cmd = NULL; + size_t cmd_lvl = SHELL_CMD_ROOT_LVL; + size_t cmd_with_handler_lvl = 0; + bool wildcard_found = false; + size_t cmd_idx; + size_t argc; + char quote; + + shell_op_cursor_end_move(shell); + if (!shell_cursor_in_empty_line(shell)) { + cursor_next_line_move(shell); } - cmd = get_internal(argv[0]); - if (cmd) { - goto done; + memset(&shell->ctx->active_cmd, 0, sizeof(shell->ctx->active_cmd)); + + cmd_trim(shell); + + history_put(shell, shell->ctx->cmd_buff, + shell->ctx->cmd_buff_len); + + if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) { + shell_wildcard_prepare(shell); } - cmd = get_standalone(argv[0]); - if (cmd) { - goto done; + /* create argument list */ + quote = shell_make_argv(&argc, &argv[0], shell->ctx->cmd_buff, + CONFIG_SHELL_ARGC_MAX); + + if (!argc) { + return; } - if (argc == 1 && !default_module && NUM_OF_SHELL_CMDS == 0) { - printk("No module selected. Use 'select' or 'help'.\n"); - return -EINVAL; + if (quote != 0) { + shell_fprintf(shell, SHELL_ERROR, "not terminated: %c\r\n", + quote); + return; } - if (default_module) { - cmd = get_module_cmd(default_module, argv[0]); + /* Searching for a matching root command. */ + p_cmd = root_cmd_find(argv[0]); + if (p_cmd == NULL) { + shell_fprintf(shell, SHELL_ERROR, "%s%s\r\n", argv[0], + SHELL_MSG_COMMAND_NOT_FOUND); + return; } - if (!cmd && argc > 1) { - struct shell_module *module; + /* Root command shall be always static. */ + assert(p_cmd->is_dynamic == false); + + /* checking if root command has a handler */ + shell->ctx->active_cmd = *p_cmd->u.entry; - module = get_destination_module(argv[0]); - if (module) { - cmd = get_module_cmd(module, argv[1]); - if (cmd) { - argc--; - argv_start++; + p_cmd = p_cmd->u.entry->subcmd; + cmd_lvl++; + cmd_idx = 0; + + /* Below loop is analyzing subcommands of found root command. */ + while (true) { + if (cmd_lvl >= argc) { + break; + } + + if (!strcmp(argv[cmd_lvl], "-h") || + !strcmp(argv[cmd_lvl], "--help")) { + /* Command called with help option so it makes no sense + * to search deeper commands. + */ + help_flag_set(shell); + break; + } + + if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) { + enum shell_wildcard_status status; + + status = shell_wildcard_process(shell, p_cmd, + argv[cmd_lvl]); + /* Wildcard character found but there is no matching + * command. + */ + if (status == SHELL_WILDCARD_CMD_NO_MATCH_FOUND) { + break; + } + + /* Wildcard character was not found function can process + * argument. + */ + if (status != SHELL_WILDCARD_NOT_FOUND) { + ++cmd_lvl; + wildcard_found = true; + continue; } } + + cmd_get(p_cmd, cmd_lvl, cmd_idx++, &p_static_entry, &d_entry); + + if ((cmd_idx == 0) || (p_static_entry == NULL)) { + break; + } + + if (strcmp(argv[cmd_lvl], p_static_entry->syntax) == 0) { + /* checking if command has a handler */ + if (p_static_entry->handler != NULL) { + if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) { + if (wildcard_found) { + shell_op_cursor_end_move(shell); + shell_op_cond_next_line(shell); + + /* An error occurred, fnmatch + * argument cannot be followed + * by argument with a handler to + * avoid multiple function + * calls. + */ + shell_fprintf(shell, + SHELL_ERROR, + "Error: requested" + " multiple function" + " executions\r\n"); + help_flag_clear(shell); + + return; + } + } + + shell->ctx->active_cmd = *p_static_entry; + cmd_with_handler_lvl = cmd_lvl; + } + + cmd_lvl++; + cmd_idx = 0; + p_cmd = p_static_entry->subcmd; + } } - if (!cmd) { - if (app_cmd_handler) { - return app_cmd_handler(argc, argv); + if (IS_ENABLED(CONFIG_SHELL_WILDCARD)) { + shell_wildcard_finalize(shell); + /* cmd_buffer has been overwritten by function finalize function + * with all expanded commands. Hence shell_make_argv needs to + * be called again. + */ + (void)shell_make_argv(&argc, &argv[0], + shell->ctx->cmd_buff, + CONFIG_SHELL_ARGC_MAX); + } + + /* Executing the deepest found handler. */ + if (shell->ctx->active_cmd.handler == NULL) { + if (shell->ctx->active_cmd.help) { + shell_help_print(shell, NULL, 0); + } else { + shell_fprintf(shell, SHELL_ERROR, + SHELL_MSG_SPECIFY_SUBCOMMAND); } + } else { + shell->ctx->active_cmd.handler(shell, + argc - cmd_with_handler_lvl, + &argv[cmd_with_handler_lvl]); + } + + help_flag_clear(shell); +} + +static void shell_transport_evt_handler(enum shell_transport_evt evt_type, + void *context) +{ + struct shell *shell = (struct shell *)context; + struct k_poll_signal *signal; + + signal = (evt_type == SHELL_TRANSPORT_EVT_RX_RDY) ? + &shell->ctx->signals[SHELL_SIGNAL_RXRDY] : + &shell->ctx->signals[SHELL_SIGNAL_TXDONE]; + k_poll_signal(signal, 0); +} + +static void shell_current_command_erase(const struct shell *shell) +{ + shell_multiline_data_calc(&shell->ctx->vt100_ctx.cons, + shell->ctx->cmd_buff_pos, + shell->ctx->cmd_buff_len); + shell_op_cursor_horiz_move(shell, -shell->ctx->vt100_ctx.cons.cur_x); + shell_op_cursor_vert_move(shell, shell->ctx->vt100_ctx.cons.cur_y - 1); + + clear_eos(shell); +} + +static void shell_current_command_print(const struct shell *shell) +{ + shell_fprintf(shell, SHELL_INFO, "%s", shell->name); + + if (flag_echo_is_set(shell)) { + shell_fprintf(shell, SHELL_NORMAL, "%s", shell->ctx->cmd_buff); + shell_op_cursor_position_synchronize(shell); + } +} + +static void shell_log_process(const struct shell *shell) +{ + bool processed; + int signaled; + int result; + + do { + shell_current_command_erase(shell); + processed = shell_log_backend_process(shell->log_backend); + shell_current_command_print(shell); + + k_poll_signal_check(&shell->ctx->signals[SHELL_SIGNAL_RXRDY], + &signaled, &result); - printk("Unrecognized command: %s\n", argv[0]); - printk("Type 'help' for list of available commands\n"); - return -EINVAL; + } while (processed && !signaled); +} + +static int shell_instance_init(const struct shell *shell, const void *p_config, + bool use_colors) +{ + assert(shell); + assert(shell->ctx && shell->iface && shell->name); + assert((shell->newline_char == '\n') || (shell->newline_char == '\r')); + + int err; + + err = shell->iface->api->init(shell->iface, p_config, + shell_transport_evt_handler, + (void *) shell); + if (err != 0) { + return err; + } + + history_init(shell); + + memset(shell->ctx, 0, sizeof(*shell->ctx)); + + if (IS_ENABLED(CONFIG_SHELL_BACKSPACE_MODE_DELETE)) { + shell->ctx->internal.flags.mode_delete = 1; } -done: - err = cmd->cb(argc, argv_start); - if (err < 0) { - show_cmd_help(cmd, false); + if (IS_ENABLED(CONFIG_SHELL_STATS)) { + shell->stats->log_lost_cnt = 0; } - return err; + shell->ctx->internal.flags.tx_rdy = 1; + shell->ctx->internal.flags.echo = CONFIG_SHELL_ECHO_STATUS; + shell->ctx->state = SHELL_STATE_INITIALIZED; + shell->ctx->vt100_ctx.cons.terminal_wid = SHELL_DEFAULT_TERMINAL_WIDTH; + shell->ctx->vt100_ctx.cons.terminal_hei = SHELL_DEFAULT_TERMINAL_HEIGHT; + shell->ctx->vt100_ctx.cons.name_len = shell_strlen(shell->name); + shell->ctx->internal.flags.use_colors = + IS_ENABLED(CONFIG_SHELL_VT100_COLORS); + + return 0; } -static void shell(void *p1, void *p2, void *p3) +static int shell_instance_uninit(const struct shell *shell); + +void shell_thread(void *shell_handle, void *dummy1, void *dummy2) { - bool skip_prompt = false; + struct shell *shell = (struct shell *)shell_handle; + int err; + int i; - ARG_UNUSED(p1); - ARG_UNUSED(p2); - ARG_UNUSED(p3); + for (i = 0; i < SHELL_SIGNALS; i++) { + k_poll_signal_init(&shell->ctx->signals[i]); + k_poll_event_init(&shell->ctx->events[i], + K_POLL_TYPE_SIGNAL, + K_POLL_MODE_NOTIFY_ONLY, + &shell->ctx->signals[i]); + } - printk("Zephyr Shell, Zephyr version: %s\n", KERNEL_VERSION_STRING); - printk("Type 'help' for a list of available commands\n"); - while (1) { - struct console_input *cmd; + err = shell_start(shell); + if (err != 0) { + return; + } - if (!no_promt && !skip_prompt) { - printk("%s", get_prompt()); -#if defined(CONFIG_NATIVE_POSIX_CONSOLE) - /* The native printk driver is line buffered */ - posix_flush_stdout(); -#endif - } + while (true) { + int signaled; + int result; - cmd = k_fifo_get(&cmds_queue, K_FOREVER); + err = k_poll(shell->ctx->events, SHELL_SIGNALS, K_FOREVER); + (void)err; - /* If the received line is an mcumgr frame, divert it to the - * mcumgr handler. Don't print the shell prompt this time, as - * that will interfere with the mcumgr response. - */ - if (mcumgr_cmd_handler != NULL && cmd->is_mcumgr) { - mcumgr_cmd_handler(cmd->line, mcumgr_arg); - skip_prompt = true; - } else { - shell_exec(cmd->line); - skip_prompt = false; + k_poll_signal_check(&shell->ctx->signals[SHELL_SIGNAL_KILL], + &signaled, &result); + + if (signaled) { + k_poll_signal_reset( + &shell->ctx->signals[SHELL_SIGNAL_KILL]); + (void)shell_instance_uninit(shell); + + k_thread_abort(k_current_get()); } - k_fifo_put(&avail_queue, cmd); + k_poll_signal_check(&shell->ctx->signals[SHELL_SIGNAL_LOG_MSG], + &signaled, &result); + + if (!signaled) { + /* Other signals handled together.*/ + k_poll_signal_reset( + &shell->ctx->signals[SHELL_SIGNAL_RXRDY]); + k_poll_signal_reset( + &shell->ctx->signals[SHELL_SIGNAL_TXDONE]); + shell_process(shell); + } else if (IS_ENABLED(CONFIG_LOG)) { + k_poll_signal_reset( + &shell->ctx->signals[SHELL_SIGNAL_LOG_MSG]); + /* process log msg */ + shell_log_process(shell); + } } } -static struct shell_module *get_completion_module(char *str, - char **command_prefix) +int shell_init(const struct shell *shell, const void *transport_config, + bool use_colors, bool log_backend, u32_t init_log_level) { - char dest_str[MODULE_NAME_MAX_LEN]; - struct shell_module *dest; - char *start; + assert(shell); + int err; - /* remove ' ' at the beginning of the line */ - while (*str && *str == ' ') { - str++; + err = shell_instance_init(shell, transport_config, use_colors); + if (err != 0) { + return err; } - if (!*str) { - return NULL; + if (log_backend) { + if (IS_ENABLED(CONFIG_LOG)) { + shell_log_backend_enable(shell->log_backend, + (void *)shell, init_log_level); + } } - start = str; + (void)k_thread_create(shell->thread, + shell->stack, CONFIG_SHELL_STACK_SIZE, + shell_thread, (void *)shell, NULL, NULL, + CONFIG_SHELL_THREAD_PRIO, 0, K_NO_WAIT); + + return 0; +} + +static int shell_instance_uninit(const struct shell *shell) +{ + assert(shell); + assert(shell->ctx && shell->iface && shell->name); + int err; + + if (flag_processing_is_set(shell)) { + return -EBUSY; + } + + if (IS_ENABLED(CONFIG_LOG)) { + /* todo purge log queue */ + shell_log_backend_disable(shell->log_backend); + } + + err = shell->iface->api->uninit(shell->iface); + if (err != 0) { + return err; + } + + history_purge(shell); + + shell->ctx->state = SHELL_STATE_UNINITIALIZED; + + return 0; +} + +int shell_uninit(const struct shell *shell) +{ + if (IS_ENABLED(CONFIG_MULTITHREADING)) { + /* signal kill message */ + (void)k_poll_signal(&shell->ctx->signals[SHELL_SIGNAL_KILL], 0); - if (default_module) { - dest = default_module; - /* caller function already checks str len and put '\0' */ - *command_prefix = str; + return 0; } else { - dest = NULL; + return shell_instance_uninit(shell); } +} - /* - * In case of a default module: only one parameter is possible. - * Otherwise, only two parameters are possibles. - */ - str = strchr(str, ' '); - if (default_module) { - return str ? NULL : dest; +int shell_start(const struct shell *shell) +{ + assert(shell); + assert(shell->ctx && shell->iface && shell->name); + int err; + + if (shell->ctx->state != SHELL_STATE_INITIALIZED) { + return -ENOTSUP; + } + + err = shell->iface->api->enable(shell->iface, false); + if (err != 0) { + return err; } - if (!str) { - return NULL; + if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS_ENABLED)) { + vt100_color_set(shell, SHELL_NORMAL); } - if ((str - start + 1) >= MODULE_NAME_MAX_LEN) { - return NULL; + shell_raw_fprintf(shell->fprintf_ctx, "\r\n\n"); + + shell_state_set(shell, SHELL_STATE_ACTIVE); + + return 0; +} + +int shell_stop(const struct shell *shell) +{ + assert(shell); + + if ((shell->ctx->state == SHELL_STATE_INITIALIZED) || + (shell->ctx->state == SHELL_STATE_UNINITIALIZED)) { + return -ENOTSUP; } - strncpy(dest_str, start, (str - start + 1)); - dest_str[str - start] = '\0'; - dest = get_destination_module(dest_str); - if (!dest) { - return NULL; + shell_state_set(shell, SHELL_STATE_INITIALIZED); + + return 0; +} + +void shell_process(const struct shell *shell) +{ + assert(shell); + + union shell_internal internal; + + internal.value = 0; + internal.flags.processing = 1; + + (void)atomic_or((atomic_t *)&shell->ctx->internal.value, + internal.value); + + switch (shell->ctx->state) { + case SHELL_STATE_UNINITIALIZED: + case SHELL_STATE_INITIALIZED: + /* Console initialized but not started. */ + break; + + case SHELL_STATE_ACTIVE: + shell_state_collect(shell); + break; + + default: + break; } - str++; + transport_buffer_flush(shell); - /* caller func has already checked str len and put '\0' at the end */ - *command_prefix = str; - str = strchr(str, ' '); + internal.value = 0xFFFFFFFF; + internal.flags.processing = 0; + (void)atomic_and((atomic_t *)&shell->ctx->internal.value, + internal.value); +} - /* only two parameters are possibles in case of no default module */ - return str ? NULL : dest; +/* Function shall be only used by the fprintf module. */ +void shell_print_stream(const void *user_ctx, const char *data, + size_t data_len) +{ + shell_write((const struct shell *) user_ctx, data, data_len); } -static u8_t completion(char *line, u8_t len) +void shell_fprintf(const struct shell *shell, enum shell_vt100_color color, + const char *p_fmt, ...) { - const char *first_match = NULL; - int common_chars = -1, space = 0; - int i, command_len; - const struct shell_module *module; - char *command_prefix; + assert(shell); - if (len >= (MODULE_NAME_MAX_LEN + COMMAND_MAX_LEN - 1)) { - return 0; + va_list args = { 0 }; + + va_start(args, p_fmt); + + if (IS_ENABLED(CONFIG_SHELL_VT100_COLORS) && + shell->ctx->internal.flags.use_colors && + (color != shell->ctx->vt100_ctx.col.col)) { + struct shell_vt100_colors col; + + vt100_colors_store(shell, &col); + vt100_color_set(shell, color); + + shell_fprintf_fmt(shell->fprintf_ctx, p_fmt, args); + + vt100_colors_restore(shell, &col); + } else { + shell_fprintf_fmt(shell->fprintf_ctx, p_fmt, args); } - /* - * line to completion is not ended by '\0' as the line that gets from - * k_fifo_get function - */ - line[len] = '\0'; - module = get_completion_module(line, &command_prefix); - if (!module) { - return 0; + va_end(args); +} + +/* Function prints a string on terminal screen with requested margin. + * It takes care to not divide words. + * shell Pointer to shell instance. + * p_str Pointer to string to be printed. + * terminal_offset Requested left margin. + * offset_first_line Add margin to the first printed line. + */ +static void formatted_text_print(const struct shell *shell, const char *str, + size_t terminal_offset, bool offset_first_line) +{ + size_t offset = 0; + size_t length; + + if (str == NULL) { + return; } - command_len = strlen(command_prefix); + if (offset_first_line) { + shell_op_cursor_horiz_move(shell, terminal_offset); + } - for (i = 0; module->commands[i].cmd_name; i++) { - int j; - if (strncmp(command_prefix, - module->commands[i].cmd_name, command_len)) { - continue; - } + /* Skipping whitespace. */ + while (isspace((int) *(str + offset))) { + ++offset; + } - if (!first_match) { - first_match = module->commands[i].cmd_name; - continue; - } + while (true) { + size_t idx = 0; + + length = shell_strlen(str) - offset; + + if (length <= + shell->ctx->vt100_ctx.cons.terminal_wid - terminal_offset) { + for (idx = 0; idx < length; idx++) { + if (*(str + offset + idx) == '\n') { + transport_buffer_flush(shell); + shell_write(shell, str + offset, idx); + offset += idx + 1; + cursor_next_line_move(shell); + shell_op_cursor_horiz_move(shell, + terminal_offset); + break; + } + } + + /* String will fit in one line. */ + shell_raw_fprintf(shell->fprintf_ctx, str + offset); - /* more commands match, print first match */ - if (first_match && (common_chars < 0)) { - printk("\n%s\n", first_match); - common_chars = strlen(first_match); + break; } - /* cut common part of matching names */ - for (j = 0; j < common_chars; j++) { - if (first_match[j] != module->commands[i].cmd_name[j]) { + /* String is longer than terminal line so text needs to + * divide in the way to not divide words. + */ + length = shell->ctx->vt100_ctx.cons.terminal_wid + - terminal_offset; + + while (true) { + /* Determining line break. */ + if (isspace((int) (*(str + offset + idx)))) { + length = idx; + if (*(str + offset + idx) == '\n') { + break; + } + } + + if ((idx + terminal_offset) >= + shell->ctx->vt100_ctx.cons.terminal_wid) { + /* End of line reached. */ break; } + + ++idx; + } + + /* Writing one line, fprintf IO buffer must be flushed + * before calling shell_write. + */ + transport_buffer_flush(shell); + shell_write(shell, str + offset, length); + offset += length; + + /* Calculating text offset to ensure that next line will + * not begin with a space. + */ + while (isspace((int) (*(str + offset)))) { + ++offset; } - common_chars = j; + cursor_next_line_move(shell); + shell_op_cursor_horiz_move(shell, terminal_offset); - printk("%s\n", module->commands[i].cmd_name); } + cursor_next_line_move(shell); +} - /* no match, do nothing */ - if (!first_match) { - return 0; - } +static void help_cmd_print(const struct shell *shell) +{ + static const char cmd_sep[] = " - "; /* commands separator */ - if (common_chars >= 0) { - /* multiple match, restore prompt */ - printk("%s", get_prompt()); - printk("%s", line); - } else { - common_chars = strlen(first_match); - space = 1; - } + u16_t field_width = shell_strlen(shell->ctx->active_cmd.syntax) + + shell_strlen(cmd_sep); - /* complete common part */ - for (i = command_len; i < common_chars; i++) { - printk("%c", first_match[i]); - line[len++] = first_match[i]; - } + shell_fprintf(shell, SHELL_NORMAL, "%s%s", + shell->ctx->active_cmd.syntax, cmd_sep); + + formatted_text_print(shell, shell->ctx->active_cmd.help, + field_width, false); +} + +static void help_item_print(const struct shell *shell, const char *item_name, + u16_t item_name_width, const char *item_help) +{ + static const u8_t tabulator[] = " "; + const u16_t offset = 2 * strlen(tabulator) + item_name_width + 1; - /* for convenience add space after command */ - if (space) { - printk(" "); - line[len] = ' '; + if (item_name == NULL) { + return; } - return common_chars - command_len + space; -} + /* print option name */ + shell_fprintf(shell, SHELL_NORMAL, "%s%-*s%s:", + tabulator, + item_name_width, item_name, + tabulator); + if (item_help == NULL) { + cursor_next_line_move(shell); + return; + } + /* print option help */ + formatted_text_print(shell, item_help, offset, false); +} -void shell_init(const char *str) +static void help_options_print(const struct shell *shell, + const struct shell_getopt_option *opt, + size_t opt_cnt) { - k_fifo_init(&cmds_queue); - k_fifo_init(&avail_queue); + static const char opt_sep[] = ", "; /* options separator */ + static const char help_opt[] = "-h, --help"; + u16_t longest_name = shell_strlen(help_opt); + + shell_fprintf(shell, SHELL_NORMAL, "Options:\r\n"); + + if ((opt == NULL) || (opt_cnt == 0)) { + help_item_print(shell, help_opt, longest_name, + "Show command help."); + return; + } - line_queue_init(); + /* Looking for the longest option string. */ + for (size_t i = 0; i < opt_cnt; ++i) { + u16_t len = shell_strlen(opt[i].optname_short) + + shell_strlen(opt[i].optname) + + shell_strlen(opt_sep); - prompt = str ? str : ""; + longest_name = len > longest_name ? len : longest_name; + } + + /* help option is printed first */ + help_item_print(shell, help_opt, longest_name, "Show command help."); - k_thread_create(&shell_thread, stack, STACKSIZE, shell, NULL, NULL, - NULL, K_PRIO_COOP(7), 0, K_NO_WAIT); + /* prepare a buffer to compose a string: + * option name short - option separator - option name + */ + memset(shell->ctx->temp_buff, 0, longest_name + 1); - /* Register console handler */ - console_register_line_input(&avail_queue, &cmds_queue, completion); + /* Formating and printing all available options (except -h, --help). */ + for (size_t i = 0; i < opt_cnt; ++i) { + if (opt[i].optname_short) { + strcpy(shell->ctx->temp_buff, opt[i].optname_short); + } + if (opt[i].optname) { + if (*shell->ctx->temp_buff) { + strcat(shell->ctx->temp_buff, opt_sep); + strcat(shell->ctx->temp_buff, opt[i].optname); + } else { + strcpy(shell->ctx->temp_buff, opt[i].optname); + } + } + help_item_print(shell, shell->ctx->temp_buff, longest_name, + opt[i].optname_help); + } } -/** @brief Optionally register an app default cmd handler. - * - * @param handler To be called if no cmd found in cmds registered with - * shell_init. +/* Function is printing command help, its subcommands name and subcommands + * help string. */ -void shell_register_app_cmd_handler(shell_cmd_function_t handler) +static void help_subcmd_print(const struct shell *shell) { - app_cmd_handler = handler; + const struct shell_static_entry *entry = NULL; + struct shell_static_entry static_entry; + u16_t longest_syntax = 0; + size_t cmd_idx = 0; + + /* Checking if there are any subcommands available. */ + if (!shell->ctx->active_cmd.subcmd) { + return; + } + + /* Searching for the longest subcommand to print. */ + do { + cmd_get(shell->ctx->active_cmd.subcmd, !SHELL_CMD_ROOT_LVL, + cmd_idx++, &entry, &static_entry); + + if (!entry) { + break; + } + + u16_t len = shell_strlen(entry->syntax); + + longest_syntax = longest_syntax > len ? longest_syntax : len; + } while (cmd_idx != 0); /* too many commands */ + + if (cmd_idx == 1) { + return; + } + + shell_fprintf(shell, SHELL_NORMAL, "Subcommands:\r\n"); + + /* Printing subcommands and help string (if exists). */ + cmd_idx = 0; + + while (true) { + cmd_get(shell->ctx->active_cmd.subcmd, !SHELL_CMD_ROOT_LVL, + cmd_idx++, &entry, &static_entry); + + if (entry == NULL) { + break; + } + + help_item_print(shell, entry->syntax, longest_syntax, + entry->help); + } } -void shell_register_prompt_handler(shell_prompt_function_t handler) +void shell_help_print(const struct shell *shell, + const struct shell_getopt_option *opt, size_t opt_len) { - app_prompt_handler = handler; + assert(shell); + + if (!IS_ENABLED(CONFIG_SHELL_HELP)) { + return; + } + + help_cmd_print(shell); + help_options_print(shell, opt, opt_len); + help_subcmd_print(shell); } -void shell_register_default_module(const char *name) +bool shell_cmd_precheck(const struct shell *shell, + bool arg_cnt_ok, + const struct shell_getopt_option *opt, + size_t opt_len) { - int err = set_default_module(name); + if (shell_help_requested(shell)) { + shell_help_print(shell, opt, opt_len); + return false; + } + + if (!arg_cnt_ok) { + shell_fprintf(shell, SHELL_ERROR, + "%s: wrong parameter count\r\n", + shell->ctx->active_cmd.syntax); - if (!err) { - printk("\n%s", default_module_prompt); + if (IS_ENABLED(SHELL_HELP_ON_WRONG_ARGUMENT_COUNT)) { + shell_help_print(shell, opt, opt_len); + } + + return false; } + + return true; } diff --git a/subsys/shell/shell_cmds.c b/subsys/shell/shell_cmds.c new file mode 100644 index 000000000000..06a0b1e3c24b --- /dev/null +++ b/subsys/shell/shell_cmds.c @@ -0,0 +1,489 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include "shell_utils.h" +#include "shell_ops.h" +#include "shell_vt100.h" + +#define SHELL_HELP_CLEAR "Clear screen." +#define SHELL_HELP_BACKSPACE_MODE "Toggle backspace key mode.\r\n" \ + "Some terminals are not sending separate escape code for" \ + "backspace and delete button. Hence backspace is not working as" \ + "expected. This command can force shell to interpret delete" \ + " escape as backspace." +#define SHELL_HELP_BACKSPACE_MODE_BACKSPACE "Set different escape" \ + " code for backspace and delete key." +#define SHELL_HELP_BACKSPACE_MODE_DELETE "Set the same escape" \ + " code for backspace and delete key." + +#define SHELL_HELP_COLORS "Toggle colored syntax." +#define SHELL_HELP_COLORS_OFF "Disable colored syntax." +#define SHELL_HELP_COLORS_ON "Enable colored syntax." +#define SHELL_HELP_STATISTICS "Shell statistics." +#define SHELL_HELP_STATISTICS_SHOW \ + "Get shell statistics for the Logger module." +#define SHELL_HELP_STATISTICS_RESET \ + "Reset shell statistics for the Logger module." +#define SHELL_HELP_RESIZE \ + "Console gets terminal screen size or assumes 80 in case " \ + "the readout fails. It must be executed after each terminal " \ + "width change to ensure correct text display." +#define SHELL_HELP_RESIZE_DEFAULT \ + "Assume 80 chars screen width and send this setting " \ + "to the terminal." +#define SHELL_HELP_HISTORY "Command history." +#define SHELL_HELP_ECHO "Toggle shell echo." +#define SHELL_HELP_ECHO_ON "Enable shell echo." +#define SHELL_HELP_ECHO_OFF \ + "Disable shell echo. Arrows and buttons: Backspace, Delete, End, " \ + "Home, Insert are not handled." +#define SHELL_HELP_SHELL "Useful, not Unix-like shell commands." + +#define SHELL_MSG_UNKNOWN_PARAMETER " unknown parameter: " + +#define SHELL_MAX_TERMINAL_SIZE (250u) + +/* 10 == {esc, [, 2, 5, 0, ;, 2, 5, 0, '\0'} */ +#define SHELL_CURSOR_POSITION_BUFFER (10u) + +/* Function reads cursor position from terminal. */ +static int cursor_position_get(const struct shell *shell, u16_t *x, u16_t *y) +{ + u16_t buff_idx = 0; + size_t cnt; + char c = 0; + + *x = 0; + *y = 0; + + memset(shell->ctx->temp_buff, 0, sizeof(shell->ctx->temp_buff)); + + /* escape code asking terminal about its size */ + static char const cmd_get_terminal_size[] = "\033[6n"; + + shell_raw_fprintf(shell->fprintf_ctx, cmd_get_terminal_size); + + /* fprintf buffer needs to be flushed to start sending prepared + * escape code to the terminal. + */ + shell_fprintf_buffer_flush(shell->fprintf_ctx); + + /* timeout for terminal response = ~1s */ + for (u16_t i = 0; i < 1000; i++) { + do { + (void)shell->iface->api->read(shell->iface, &c, + sizeof(c), &cnt); + if (cnt == 0) { + k_sleep(1); + break; + } + if ((c != SHELL_VT100_ASCII_ESC) && + (shell->ctx->temp_buff[0] != + SHELL_VT100_ASCII_ESC)) { + continue; + } + + if (c == 'R') { /* End of response from the terminal. */ + shell->ctx->temp_buff[buff_idx] = '\0'; + if (shell->ctx->temp_buff[1] != '[') { + shell->ctx->temp_buff[0] = 0; + return -EIO; + } + + /* Index start position in the buffer where 'y' + * is stored. + */ + buff_idx = 2; + + while (shell->ctx->temp_buff[buff_idx] != ';') { + *y = *y * 10 + + (shell->ctx->temp_buff[buff_idx++] - + '0'); + if (buff_idx >= + CONFIG_SHELL_CMD_BUFF_SIZE) { + return -EMSGSIZE; + } + } + + if (++buff_idx >= CONFIG_SHELL_CMD_BUFF_SIZE) { + return -EIO; + } + + while (shell->ctx->temp_buff[buff_idx] + != '\0') { + *x = *x * 10 + + (shell->ctx->temp_buff[buff_idx++] - + '0'); + + if (buff_idx >= + CONFIG_SHELL_CMD_BUFF_SIZE) { + return -EMSGSIZE; + } + } + /* horizontal cursor position */ + if (*x > SHELL_MAX_TERMINAL_SIZE) { + *x = SHELL_MAX_TERMINAL_SIZE; + } + + /* vertical cursor position */ + if (*y > SHELL_MAX_TERMINAL_SIZE) { + *y = SHELL_MAX_TERMINAL_SIZE; + } + + shell->ctx->temp_buff[0] = 0; + + return 0; + } + + shell->ctx->temp_buff[buff_idx] = c; + + if (++buff_idx > SHELL_CURSOR_POSITION_BUFFER - 1) { + shell->ctx->temp_buff[0] = 0; + /* data_buf[SHELL_CURSOR_POSITION_BUFFER - 1] + * is reserved for '\0' + */ + return -ENOMEM; + } + + } while (cnt > 0); + } + + return -ETIMEDOUT; +} + +/* Function gets terminal width and height. */ +static int terminal_size_get(const struct shell *shell) +{ + u16_t x; /* horizontal position */ + u16_t y; /* vertical position */ + int ret_val = 0; + + cursor_save(shell); + + /* Assumption: terminal width and height < 999. */ + /* Move to last column. */ + shell_op_cursor_vert_move(shell, -SHELL_MAX_TERMINAL_SIZE); + /* Move to last row. */ + shell_op_cursor_horiz_move(shell, SHELL_MAX_TERMINAL_SIZE); + + if (cursor_position_get(shell, &x, &y) == 0) { + shell->ctx->vt100_ctx.cons.terminal_wid = x; + shell->ctx->vt100_ctx.cons.terminal_hei = y; + } else { + ret_val = -ENOTSUP; + } + + cursor_restore(shell); + + return ret_val; +} + +static void cmd_clear(const struct shell *shell, size_t argc, char **argv) +{ + (void)argv; + + if ((argc == 2) && (shell_help_requested(shell))) { + shell_help_print(shell, NULL, 0); + return; + } + SHELL_VT100_CMD(shell, SHELL_VT100_CURSORHOME); + SHELL_VT100_CMD(shell, SHELL_VT100_CLEARSCREEN); +} + +static void cmd_shell(const struct shell *shell, size_t argc, char **argv) +{ + (void)argv; + + if ((argc == 1) || ((argc == 2) && shell_help_requested(shell))) { + shell_help_print(shell, NULL, 0); + return; + } + + shell_fprintf(shell, SHELL_ERROR, SHELL_MSG_SPECIFY_SUBCOMMAND); +} + +static void cmd_bacskpace_mode(const struct shell *shell, size_t argc, + char **argv) +{ + (void)shell_cmd_precheck(shell, (argc == 2), NULL, 0); +} + +static void cmd_bacskpace_mode_backspace(const struct shell *shell, size_t argc, + char **argv) +{ + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + + shell->ctx->internal.flags.mode_delete = 0; +} + +static void cmd_bacskpace_mode_delete(const struct shell *shell, size_t argc, + char **argv) +{ + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + + shell->ctx->internal.flags.mode_delete = 1; +} + +static void cmd_colors_off(const struct shell *shell, size_t argc, char **argv) +{ + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + + shell->ctx->internal.flags.use_colors = 0; +} + +static void cmd_colors_on(const struct shell *shell, size_t argc, char **argv) +{ + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + shell->ctx->internal.flags.use_colors = 1; +} + +static void cmd_colors(const struct shell *shell, size_t argc, char **argv) +{ + if (argc == 1) { + shell_help_print(shell, NULL, 0); + return; + } + + if (!shell_cmd_precheck(shell, (argc == 2), NULL, 0)) { + return; + } + + shell_fprintf(shell, SHELL_ERROR, "%s:%s%s\r\n", argv[0], + SHELL_MSG_UNKNOWN_PARAMETER, argv[1]); +} + +static void cmd_echo(const struct shell *shell, size_t argc, char **argv) +{ + if (!shell_cmd_precheck(shell, (argc <= 2), NULL, 0)) { + return; + } + + if (argc == 2) { + shell_fprintf(shell, SHELL_ERROR, "%s:%s%s\r\n", argv[0], + SHELL_MSG_UNKNOWN_PARAMETER, argv[1]); + return; + } + + shell_fprintf(shell, SHELL_NORMAL, "Echo status: %s\r\n", + flag_echo_is_set(shell) ? "on" : "off"); +} + +static void cmd_echo_off(const struct shell *shell, size_t argc, char **argv) +{ + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + + shell->ctx->internal.flags.echo = 0; +} + +static void cmd_echo_on(const struct shell *shell, size_t argc, char **argv) +{ + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + + shell->ctx->internal.flags.echo = 1; +} + +static void cmd_help(const struct shell *shell, size_t argc, char **argv) +{ + shell_fprintf(shell, SHELL_NORMAL, "Please press the button to " + "see all available commands.\r\n" + "You can also use the button " + "to prompt or auto-complete all " + "commands or its subcommands.\r\n" + "You can try to call commands " + "with <-h> or <--help> parameter to " + "get know what they are doing.\r\n"); + +} + +static void cmd_history(const struct shell *shell, size_t argc, char **argv) +{ + size_t i = 0; + size_t len; + + if (!IS_ENABLED(CONFIG_SHELL_HISTORY)) { + shell_fprintf(shell, SHELL_ERROR, "Command not supported.\r\n"); + return; + } + + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + + while (1) { + shell_history_get(shell->history, true, + shell->ctx->temp_buff, &len); + + if (len) { + shell_fprintf(shell, SHELL_NORMAL, "[%3d] %s\r\n", + i++, shell->ctx->temp_buff); + + } else { + break; + } + } + + shell->ctx->temp_buff[0] = '\0'; +} + +static void cmd_shell_stats(const struct shell *shell, size_t argc, char **argv) +{ + if (argc == 1) { + shell_help_print(shell, NULL, 0); + return; + } + + if (argc == 2) { + shell_fprintf(shell, SHELL_ERROR, "%s:%s%s\r\n", argv[0], + SHELL_MSG_UNKNOWN_PARAMETER, argv[1]); + return; + } + + (void)shell_cmd_precheck(shell, (argc <= 2), NULL, 0); +} + +static void cmd_shell_stats_show(const struct shell *shell, size_t argc, + char **argv) +{ + if (!IS_ENABLED(CONFIG_SHELL_STATS)) { + shell_fprintf(shell, SHELL_ERROR, "Command not supported.\r\n"); + return; + } + + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + + shell_fprintf(shell, SHELL_NORMAL, "Lost logs: %u\r\n", + shell->stats->log_lost_cnt); +} + +static void cmd_shell_stats_reset(const struct shell *shell, + size_t argc, char **argv) +{ + if (!IS_ENABLED(CONFIG_SHELL_STATS)) { + shell_fprintf(shell, SHELL_ERROR, "Command not supported.\r\n"); + return; + } + + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + + shell->stats->log_lost_cnt = 0; +} + +static void cmd_resize_default(const struct shell *shell, + size_t argc, char **argv) +{ + if (!shell_cmd_precheck(shell, (argc == 1), NULL, 0)) { + return; + } + + SHELL_VT100_CMD(shell, SHELL_VT100_SETCOL_80); + shell->ctx->vt100_ctx.cons.terminal_wid = SHELL_DEFAULT_TERMINAL_WIDTH; + shell->ctx->vt100_ctx.cons.terminal_hei = SHELL_DEFAULT_TERMINAL_HEIGHT; +} + +static void cmd_resize(const struct shell *shell, size_t argc, char **argv) +{ + int err; + + if (!IS_ENABLED(CONFIG_SHELL_CMDS_RESIZE)) { + shell_fprintf(shell, SHELL_ERROR, "Command not supported.\r\n"); + return; + } + + if (!shell_cmd_precheck(shell, (argc <= 2), NULL, 0)) { + return; + } + + if (argc != 1) { + shell_fprintf(shell, SHELL_ERROR, "%s:%s%s\r\n", argv[0], + SHELL_MSG_UNKNOWN_PARAMETER, argv[1]); + return; + } + + err = terminal_size_get(shell); + if (err != 0) { + shell->ctx->vt100_ctx.cons.terminal_wid = + SHELL_DEFAULT_TERMINAL_WIDTH; + shell->ctx->vt100_ctx.cons.terminal_hei = + SHELL_DEFAULT_TERMINAL_HEIGHT; + shell_fprintf(shell, SHELL_WARNING, + "No response from the terminal, assumed 80x24 " + "screen size\r\n"); + } +} + +/* Warning! + * Subcommands must be placed in alphabetical order to ensure correct + * autocompletion. + */ +SHELL_CREATE_STATIC_SUBCMD_SET(m_sub_colors) +{ + SHELL_CMD(off, NULL, SHELL_HELP_COLORS_OFF, cmd_colors_off), + SHELL_CMD(on, NULL, SHELL_HELP_COLORS_ON, cmd_colors_on), + SHELL_SUBCMD_SET_END +}; + +SHELL_CREATE_STATIC_SUBCMD_SET(m_sub_echo) +{ + SHELL_CMD(off, NULL, SHELL_HELP_ECHO_OFF, cmd_echo_off), + SHELL_CMD(on, NULL, SHELL_HELP_ECHO_ON, cmd_echo_on), + SHELL_SUBCMD_SET_END +}; + +SHELL_CREATE_STATIC_SUBCMD_SET(m_sub_shell_stats) +{ + SHELL_CMD(reset, NULL, SHELL_HELP_STATISTICS_RESET, + cmd_shell_stats_reset), + SHELL_CMD(show, NULL, SHELL_HELP_STATISTICS_SHOW, cmd_shell_stats_show), + SHELL_SUBCMD_SET_END +}; + +SHELL_CREATE_STATIC_SUBCMD_SET(m_sub_backspace_mode) +{ + SHELL_CMD(backspace, NULL, SHELL_HELP_BACKSPACE_MODE_BACKSPACE, + cmd_bacskpace_mode_backspace), + SHELL_CMD(delete, NULL, SHELL_HELP_BACKSPACE_MODE_DELETE, + cmd_bacskpace_mode_delete), + SHELL_SUBCMD_SET_END +}; + +SHELL_CREATE_STATIC_SUBCMD_SET(m_sub_shell) +{ + SHELL_CMD(backspace_mode, &m_sub_backspace_mode, + SHELL_HELP_BACKSPACE_MODE, cmd_bacskpace_mode), + SHELL_CMD(colors, &m_sub_colors, SHELL_HELP_COLORS, cmd_colors), + SHELL_CMD(echo, &m_sub_echo, SHELL_HELP_ECHO, cmd_echo), + SHELL_CMD(stats, &m_sub_shell_stats, SHELL_HELP_STATISTICS, + cmd_shell_stats), + SHELL_SUBCMD_SET_END +}; + +SHELL_CREATE_STATIC_SUBCMD_SET(m_sub_resize) +{ + SHELL_CMD(default, NULL, SHELL_HELP_RESIZE_DEFAULT, cmd_resize_default), + SHELL_SUBCMD_SET_END +}; + +SHELL_CMD_REGISTER(clear, NULL, SHELL_HELP_CLEAR, cmd_clear); +SHELL_CMD_REGISTER(shell, &m_sub_shell, SHELL_HELP_SHELL, cmd_shell); +SHELL_CMD_REGISTER(help, NULL, NULL, cmd_help); +SHELL_CMD_REGISTER(history, NULL, SHELL_HELP_HISTORY, cmd_history); +SHELL_CMD_REGISTER(resize, &m_sub_resize, SHELL_HELP_RESIZE, cmd_resize); diff --git a/subsys/shell/shell_fprintf.c b/subsys/shell/shell_fprintf.c new file mode 100644 index 000000000000..178eafc6152b --- /dev/null +++ b/subsys/shell/shell_fprintf.c @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#ifdef CONFIG_NEWLIB_LIBC +typedef int (*out_func_t)(int c, void *ctx); +extern void _vprintk(out_func_t out, void *ctx, const char *fmt, va_list ap); +#else +extern int _prf(int (*func)(), void *dest, char *format, va_list vargs); +#endif + +static int out_func(int c, void *ctx) +{ + const struct shell_fprintf *sh_fprintf; + + sh_fprintf = (const struct shell_fprintf *)ctx; + + sh_fprintf->buffer[sh_fprintf->ctrl_blk->buffer_cnt] = (u8_t)c; + sh_fprintf->ctrl_blk->buffer_cnt++; + + if (sh_fprintf->ctrl_blk->buffer_cnt == sh_fprintf->buffer_size) { + shell_fprintf_buffer_flush(sh_fprintf); + } + + return 0; +} + +void shell_fprintf_fmt(const struct shell_fprintf *sh_fprintf, + const char *fmt, va_list args) +{ +#ifndef CONFIG_NEWLIB_LIBC + (void)_prf(out_func, (void *)sh_fprintf, (char *)fmt, args); +#else + _vprintk(out_func, (void *)sh_fprintf, fmt, args); +#endif + + if (sh_fprintf->ctrl_blk->autoflush) { + shell_fprintf_buffer_flush(sh_fprintf); + } +} + + +void shell_fprintf_buffer_flush(const struct shell_fprintf *sh_fprintf) +{ + sh_fprintf->fwrite(sh_fprintf->user_ctx, sh_fprintf->buffer, + sh_fprintf->ctrl_blk->buffer_cnt); + sh_fprintf->ctrl_blk->buffer_cnt = 0; +} diff --git a/subsys/shell/shell_history.c b/subsys/shell/shell_history.c new file mode 100644 index 000000000000..5b36b5008560 --- /dev/null +++ b/subsys/shell/shell_history.c @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +struct shell_history_item { + sys_dnode_t dnode; + u16_t len; + char data[1]; +}; + +void shell_history_mode_exit(struct shell_history *history) +{ + history->current = NULL; +} + +bool shell_history_get(struct shell_history *history, bool up, + u8_t *dst, size_t *len) +{ + struct shell_history_item *h_item; /* history item */ + sys_dnode_t *l_item; /* list item */ + + if (sys_dlist_is_empty(&history->list)) { + *len = 0; + return false; + } + + if (!up) { /* button down */ + if (history->current == NULL) { + /* Not in history mode. It is started by up button. */ + *len = 0; + + return false; + } + + l_item = sys_dlist_peek_prev_no_check(&history->list, + history->current); + } else { /* button up */ + l_item = (history->current == NULL) ? + sys_dlist_peek_head_not_empty(&history->list) : + sys_dlist_peek_next_no_check(&history->list, history->current); + + } + + history->current = l_item; + h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode); + + if (h_item) { + memcpy(dst, h_item->data, h_item->len); + *len = h_item->len; + dst[*len] = '\0'; + return true; + } + + *len = 0; + return up; +} + +static void add_to_head(struct shell_history *history, + struct shell_history_item *item, + u8_t *src, size_t len) +{ + item->len = len; + memcpy(item->data, src, len); + item->data[len] = '\0'; + sys_dlist_prepend(&history->list, &item->dnode); +} + +static void remove_from_tail(struct shell_history *history) +{ + sys_dnode_t *l_item; /* list item */ + struct shell_history_item *h_item; + + l_item = sys_dlist_peek_tail(&history->list); + sys_dlist_remove(l_item); + + h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode); + k_mem_slab_free(history->mem_slab, (void **)&l_item); +} + +void shell_history_purge(struct shell_history *history) +{ + while (!sys_dlist_is_empty(&history->list)) { + remove_from_tail(history); + } +} + +void shell_history_put(struct shell_history *history, u8_t *line, size_t len) +{ + sys_dnode_t *l_item; /* list item */ + struct shell_history_item *h_item; + + shell_history_mode_exit(history); + + if (len == 0) { + return; + } + + l_item = sys_dlist_peek_head(&history->list); + h_item = CONTAINER_OF(l_item, struct shell_history_item, dnode); + + if (h_item && + (h_item->len == len) && + (strncmp(h_item->data, line, CONFIG_SHELL_CMD_BUFF_SIZE) == 0)) { + /* Same command as before, do not store */ + return; + } + + while (k_mem_slab_alloc(history->mem_slab, (void **)&h_item, K_NO_WAIT) + != 0) { + /* if no space remove the oldest entry. */ + remove_from_tail(history); + } + + add_to_head(history, h_item, line, len); +} + +void shell_history_init(struct shell_history *history) +{ + sys_dlist_init(&history->list); + history->current = NULL; +} diff --git a/subsys/shell/shell_log_backend.c b/subsys/shell/shell_log_backend.c new file mode 100644 index 000000000000..9f2200e83df9 --- /dev/null +++ b/subsys/shell/shell_log_backend.c @@ -0,0 +1,167 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "shell_ops.h" +#include + +int shell_log_backend_output_func(u8_t *data, size_t length, void *ctx) +{ + shell_print_stream(ctx, data, length); + return length; +} + +void shell_log_backend_enable(const struct shell_log_backend *backend, + void *ctx, u32_t init_log_level) +{ + log_backend_enable(backend->backend, ctx, init_log_level); + log_output_ctx_set(backend->log_output, ctx); + backend->control_block->cnt = 0; + backend->control_block->state = SHELL_LOG_BACKEND_ENABLED; +} + +static struct log_msg *msg_from_fifo(const struct shell_log_backend *backend) +{ + struct log_msg *msg = k_fifo_get(backend->fifo, K_NO_WAIT); + + if (msg) { + atomic_dec(&backend->control_block->cnt); + } + + return msg; +} + +static void fifo_flush(const struct shell_log_backend *backend) +{ + struct log_msg *msg = msg_from_fifo(backend); + + /* Flush log messages. */ + while (msg) { + log_msg_put(msg); + msg = msg_from_fifo(backend); + } +} + +static void msg_to_fifo(const struct shell *shell, + struct log_msg *msg) +{ + atomic_val_t cnt; + + k_fifo_put(shell->log_backend->fifo, msg); + + cnt = atomic_inc(&shell->log_backend->control_block->cnt); + + /* If there is too much queued free the oldest one. */ + if (cnt >= CONFIG_SHELL_MAX_LOG_MSG_BUFFERED) { + log_msg_put(msg_from_fifo(shell->log_backend)); + if (IS_ENABLED(CONFIG_SHELL_STATS)) { + shell->stats->log_lost_cnt++; + } + } +} + +void shell_log_backend_disable(const struct shell_log_backend *backend) +{ + fifo_flush(backend); + log_backend_disable(backend->backend); + backend->control_block->state = SHELL_LOG_BACKEND_DISABLED; +} + +static void msg_process(const struct log_output *log_output, + struct log_msg *msg) +{ + u32_t flags = 0; + + if (IS_ENABLED(CONFIG_SHELL)) { + flags |= LOG_OUTPUT_FLAG_COLORS; + } + + if (IS_ENABLED(CONFIG_SHELL)) { + flags |= LOG_OUTPUT_FLAG_FORMAT_TIMESTAMP; + } + + log_output_msg_process(log_output, msg, flags); + log_msg_put(msg); +} + +bool shell_log_backend_process(const struct shell_log_backend *backend) +{ + const struct shell *shell = + (const struct shell *)backend->backend->cb->ctx; + struct log_msg *msg = msg_from_fifo(backend); + + if (!msg) { + return false; + } + + msg_process(shell->log_backend->log_output, msg); + + return true; +} + +static void put(const struct log_backend *const backend, struct log_msg *msg) +{ + const struct shell *shell = (const struct shell *)backend->cb->ctx; + struct k_poll_signal *signal; + + log_msg_get(msg); + + switch (shell->log_backend->control_block->state) { + case SHELL_LOG_BACKEND_ENABLED: + msg_to_fifo(shell, msg); + + if (IS_ENABLED(CONFIG_MULTITHREADING)) { + signal = &shell->ctx->signals[SHELL_SIGNAL_LOG_MSG]; + k_poll_signal(signal, 0); + } + + break; + + case SHELL_LOG_BACKEND_PANIC: + msg_process(shell->log_backend->log_output, msg); + break; + + case SHELL_LOG_BACKEND_DISABLED: + /* fall through */ + /* no break */ + default: + /* Discard message. */ + log_msg_put(msg); + } +} + +static void panic(const struct log_backend *const backend) +{ + const struct shell *shell = (const struct shell *)backend->cb->ctx; + int err; + + err = shell->iface->api->enable(shell->iface, true); + + if (err == 0) { + shell->log_backend->control_block->state = + SHELL_LOG_BACKEND_PANIC; + + /* Move to the start of next line. */ + shell_multiline_data_calc(&shell->ctx->vt100_ctx.cons, + shell->ctx->cmd_buff_pos, + shell->ctx->cmd_buff_len); + shell_op_cursor_vert_move(shell, -1); + shell_op_cursor_horiz_move(shell, + -shell->ctx->vt100_ctx.cons.cur_x); + + while (shell_log_backend_process(shell->log_backend)) { + /* empty */ + } + } else { + shell_log_backend_disable(shell->log_backend); + } +} + +const struct log_backend_api log_backend_shell_api = { + .put = put, + .panic = panic +}; diff --git a/subsys/shell/shell_ops.c b/subsys/shell/shell_ops.c new file mode 100644 index 000000000000..561e20e8b628 --- /dev/null +++ b/subsys/shell/shell_ops.c @@ -0,0 +1,264 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "shell_ops.h" + +void shell_op_cursor_vert_move(const struct shell *shell, s32_t delta) +{ + if (delta != 0) { + shell_raw_fprintf(shell->fprintf_ctx, "\033[%d%c", + delta > 0 ? delta : -delta, + delta > 0 ? 'A' : 'B'); + } +} + +void shell_op_cursor_horiz_move(const struct shell *shell, s32_t delta) +{ + if (delta != 0) { + shell_raw_fprintf(shell->fprintf_ctx, "\033[%d%c", + delta > 0 ? delta : -delta, + delta > 0 ? 'C' : 'D'); + } +} + +/* Function returns true if command length is equal to multiplicity of terminal + * width. + */ +static inline bool full_line_cmd(const struct shell *shell) +{ + return ((shell->ctx->cmd_buff_len + shell_strlen(shell->name)) + % shell->ctx->vt100_ctx.cons.terminal_wid == 0); +} + +/* Function returns true if cursor is at beginning of an empty line. */ +bool shell_cursor_in_empty_line(const struct shell *shell) +{ + return ((shell->ctx->cmd_buff_pos + shell_strlen(shell->name)) + % shell->ctx->vt100_ctx.cons.terminal_wid == 0); +} + +void shell_op_cond_next_line(const struct shell *shell) +{ + if (shell_cursor_in_empty_line(shell) || full_line_cmd(shell)) { + cursor_next_line_move(shell); + } +} + +void shell_op_cursor_position_synchronize(const struct shell *shell) +{ + struct shell_multiline_cons *cons = &shell->ctx->vt100_ctx.cons; + bool last_line; + + shell_multiline_data_calc(cons, shell->ctx->cmd_buff_pos, + shell->ctx->cmd_buff_len); + last_line = (cons->cur_y == cons->cur_y_end); + + /* In case cursor reaches the bottom line of a terminal, it will + * be moved to the next line. + */ + if (full_line_cmd(shell)) { + cursor_next_line_move(shell); + } + + if (last_line) { + shell_op_cursor_horiz_move(shell, cons->cur_x - + cons->cur_x_end); + } else { + shell_op_cursor_vert_move(shell, cons->cur_y_end - cons->cur_y); + shell_op_cursor_horiz_move(shell, cons->cur_x - + cons->cur_x_end); + } +} + +void shell_op_cursor_move(const struct shell *shell, s16_t val) +{ + struct shell_multiline_cons *cons = &shell->ctx->vt100_ctx.cons; + u16_t new_pos = shell->ctx->cmd_buff_pos + val; + s32_t row_span; + s32_t col_span; + + shell_multiline_data_calc(cons, shell->ctx->cmd_buff_pos, + shell->ctx->cmd_buff_len); + + /* Calculate the new cursor. */ + row_span = row_span_with_buffer_offsets_get(&shell->ctx->vt100_ctx.cons, + shell->ctx->cmd_buff_pos, + new_pos); + col_span = column_span_with_buffer_offsets_get( + &shell->ctx->vt100_ctx.cons, + shell->ctx->cmd_buff_pos, + new_pos); + + shell_op_cursor_vert_move(shell, -row_span); + shell_op_cursor_horiz_move(shell, col_span); + shell->ctx->cmd_buff_pos = new_pos; +} + +void shell_op_word_remove(const struct shell *shell) +{ + char *str = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos - 1]; + char *str_start = &shell->ctx->cmd_buff[0]; + u16_t chars_to_delete; + + /* Line must not be empty and cursor must not be at 0 to continue. */ + if ((shell->ctx->cmd_buff_len == 0) || + (shell->ctx->cmd_buff_pos == 0)) { + return; + } + + /* Start at the current position. */ + chars_to_delete = 0; + + /* Look back for all spaces then for non-spaces. */ + while ((str >= str_start) && (*str == ' ')) { + ++chars_to_delete; + --str; + } + + while ((str >= str_start) && (*str != ' ')) { + ++chars_to_delete; + --str; + } + + /* Manage the buffer. */ + memmove(str + 1, str + 1 + chars_to_delete, + shell->ctx->cmd_buff_len - chars_to_delete); + shell->ctx->cmd_buff_len -= chars_to_delete; + shell->ctx->cmd_buff[shell->ctx->cmd_buff_len] = '\0'; + + /* Update display. */ + shell_op_cursor_move(shell, -chars_to_delete); + cursor_save(shell); + shell_fprintf(shell, SHELL_NORMAL, "%s", str + 1); + clear_eos(shell); + cursor_restore(shell); +} + +void shell_op_cursor_home_move(const struct shell *shell) +{ + shell_op_cursor_move(shell, -shell->ctx->cmd_buff_pos); +} + +void shell_op_cursor_end_move(const struct shell *shell) +{ + shell_op_cursor_move(shell, shell->ctx->cmd_buff_len - + shell->ctx->cmd_buff_pos); +} + + +void shell_op_left_arrow(const struct shell *shell) +{ + if (shell->ctx->cmd_buff_pos > 0) { + shell_op_cursor_move(shell, -1); + } +} + +void shell_op_right_arrow(const struct shell *shell) +{ + if (shell->ctx->cmd_buff_pos < shell->ctx->cmd_buff_len) { + shell_op_cursor_move(shell, 1); + } +} + +static void reprint_from_cursor(const struct shell *shell, u16_t diff, + bool data_removed) +{ + /* Clear eos is needed only when newly printed command is shorter than + * previously printed command. This can happen when delete or backspace + * was called. + * + * Such condition is useful for Bluetooth devices to save number of + * bytes transmitted between terminal and device. + */ + if (data_removed) { + clear_eos(shell); + } + + shell_fprintf(shell, SHELL_NORMAL, "%s", + &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos]); + shell->ctx->cmd_buff_pos = shell->ctx->cmd_buff_len; + + if (full_line_cmd(shell)) { + if (((data_removed) && (diff > 0)) || (!data_removed)) { + cursor_next_line_move(shell); + } + } + + shell_op_cursor_move(shell, -diff); +} + +static void data_insert(const struct shell *shell, const char *data, u16_t len) +{ + u16_t after = shell->ctx->cmd_buff_len - shell->ctx->cmd_buff_pos; + char *curr_pos = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos]; + + if ((shell->ctx->cmd_buff_len + len) >= CONFIG_SHELL_CMD_BUFF_SIZE) { + return; + } + + memmove(curr_pos + len, curr_pos, after); + memcpy(curr_pos, data, len); + shell->ctx->cmd_buff_len += len; + shell->ctx->cmd_buff[shell->ctx->cmd_buff_len] = '\0'; + + if (!flag_echo_is_set(shell)) { + shell->ctx->cmd_buff_pos += len; + return; + } + + reprint_from_cursor(shell, after, false); +} + +void char_replace(const struct shell *shell, char data) +{ + shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos++] = data; + shell_raw_fprintf(shell->fprintf_ctx, "%c", data); + if (shell_cursor_in_empty_line(shell)) { + cursor_next_line_move(shell); + } +} + +void shell_op_char_insert(const struct shell *shell, char data) +{ + if (shell->ctx->internal.flags.insert_mode && + (shell->ctx->cmd_buff_len != shell->ctx->cmd_buff_pos)) { + char_replace(shell, data); + } else { + data_insert(shell, &data, 1); + } +} + +void shell_op_char_backspace(const struct shell *shell) +{ + if ((shell->ctx->cmd_buff_len == 0) || + (shell->ctx->cmd_buff_pos == 0)) { + return; + } + + shell_op_cursor_move(shell, -1); + shell_op_char_delete(shell); +} + +void shell_op_char_delete(const struct shell *shell) +{ + u16_t diff = shell->ctx->cmd_buff_len - shell->ctx->cmd_buff_pos; + char *str = &shell->ctx->cmd_buff[shell->ctx->cmd_buff_pos]; + + if (diff == 0) { + return; + } + + memmove(str, str + 1, diff); + --shell->ctx->cmd_buff_len; + reprint_from_cursor(shell, --diff, true); +} + +void shell_op_completion_insert(const struct shell *shell, + const char *compl, + u16_t compl_len) +{ + data_insert(shell, compl, compl_len); +} diff --git a/subsys/shell/shell_ops.h b/subsys/shell/shell_ops.h new file mode 100644 index 000000000000..c87db7ab8fa2 --- /dev/null +++ b/subsys/shell/shell_ops.h @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef SHELL_OPS_H__ +#define SHELL_OPS_H__ + +#include +#include +#include "shell_vt100.h" +#include "shell_utils.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define SHELL_DEFAULT_TERMINAL_WIDTH (80u) /* Default PuTTY width. */ +#define SHELL_DEFAULT_TERMINAL_HEIGHT (24u) /* Default PuTTY height. */ + +static inline void shell_raw_fprintf(const struct shell_fprintf *const ctx, + const char *fmt, ...) +{ + va_list args; + + va_start(args, fmt); + shell_fprintf_fmt(ctx, fmt, args); + va_end(args); +} + +/* Macro to send VT100 commands. */ +#define SHELL_VT100_CMD(_shell_, _cmd_) \ + do { \ + static const char cmd[] = _cmd_; \ + shell_raw_fprintf(_shell_->fprintf_ctx, "%s", cmd); \ + } while (0) + +/* Function sends VT100 command to clear the screen from cursor position to + * end of the screen. + */ +static inline void clear_eos(const struct shell *shell) +{ + SHELL_VT100_CMD(shell, SHELL_VT100_CLEAREOS); +} + +/* Function sends VT100 command to save cursor position. */ +static inline void cursor_save(const struct shell *shell) +{ + SHELL_VT100_CMD(shell, SHELL_VT100_SAVECURSOR); +} + +/* Function sends VT100 command to restore saved cursor position. */ +static inline void cursor_restore(const struct shell *shell) +{ + SHELL_VT100_CMD(shell, SHELL_VT100_RESTORECURSOR); +} + +/* Function forcing new line - cannot be replaced with function + * cursor_down_move. + */ +static inline void cursor_next_line_move(const struct shell *shell) +{ + shell_raw_fprintf(shell->fprintf_ctx, "\r\n"); +} + +/* Function sends 1 character to the shell instance. */ +static inline void shell_putc(const struct shell *shell, char ch) +{ + shell_raw_fprintf(shell->fprintf_ctx, "%c", ch); +} + +static inline bool flag_echo_is_set(const struct shell *shell) +{ + return shell->ctx->internal.flags.echo == 1 ? true : false; +} + +void shell_op_cursor_vert_move(const struct shell *shell, s32_t delta); +void shell_op_cursor_horiz_move(const struct shell *shell, s32_t delta); + +void shell_op_cond_next_line(const struct shell *shell); + +/* Function will move cursor back to position == cmd_buff_pos. Example usage is + * when cursor needs to be moved back after printing some text. This function + * cannot be used to move cursor to new location by manual change of + * cmd_buff_pos. + */ +void shell_op_cursor_position_synchronize(const struct shell *shell); + +void shell_op_cursor_move(const struct shell *shell, s16_t val); + +void shell_op_left_arrow(const struct shell *shell); + +void shell_op_right_arrow(const struct shell *shell); + +/* + * Removes the "word" to the left of the cursor: + * - if there are spaces at the cursor position, remove all spaces to the left + * - remove the non-spaces (word) until a space is found or a beginning of + * buffer + */ +void shell_op_word_remove(const struct shell *shell); + +/* Function moves cursor to begin of command position, just after console + * name. + */ +void shell_op_cursor_home_move(const struct shell *shell); + +/* Function moves cursor to end of command. */ +void shell_op_cursor_end_move(const struct shell *shell); + +void char_replace(const struct shell *shell, char data); + +void shell_op_char_insert(const struct shell *shell, char data); + +void shell_op_char_backspace(const struct shell *shell); + +void shell_op_char_delete(const struct shell *shell); + +void shell_op_completion_insert(const struct shell *shell, + const char *compl, + u16_t compl_len); + +bool shell_cursor_in_empty_line(const struct shell *shell); +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_OPS_H__ */ diff --git a/subsys/shell/shell_service.c b/subsys/shell/shell_service.c index be5f3c6b1ec8..0b6e2f8f7083 100644 --- a/subsys/shell/shell_service.c +++ b/subsys/shell/shell_service.c @@ -13,7 +13,7 @@ */ #include -#include +#include #include #define SHELL_PROMPT "shell> " diff --git a/subsys/shell/shell_uart.c b/subsys/shell/shell_uart.c new file mode 100644 index 000000000000..adbb33895f81 --- /dev/null +++ b/subsys/shell/shell_uart.c @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +static void timer_handler(struct k_timer *timer) +{ + struct shell_uart *sh_uart = + CONTAINER_OF(timer, struct shell_uart, timer); + + if (uart_poll_in(sh_uart->dev, sh_uart->rx) == 0) { + sh_uart->rx_cnt = 1; + sh_uart->handler(SHELL_TRANSPORT_EVT_RX_RDY, sh_uart->context); + } +} + + +static int init(const struct shell_transport *transport, + const void *config, + shell_transport_handler_t evt_handler, + void *context) +{ + struct shell_uart *sh_uart = (struct shell_uart *)transport->ctx; + + sh_uart->dev = device_get_binding(CONFIG_UART_CONSOLE_ON_DEV_NAME); + sh_uart->handler = evt_handler; + sh_uart->context = context; + + k_timer_init(&sh_uart->timer, timer_handler, NULL); + + k_timer_start(&sh_uart->timer, 20, 20); + + return 0; +} + +static int uninit(const struct shell_transport *transport) +{ + return 0; +} + +static int enable(const struct shell_transport *transport, bool blocking) +{ + return 0; +} + +static int write(const struct shell_transport *transport, + const void *data, size_t length, size_t *cnt) +{ + struct shell_uart *sh_uart = (struct shell_uart *)transport->ctx; + const u8_t *data8 = (const u8_t *)data; + + for (size_t i = 0; i < length; i++) { + uart_poll_out(sh_uart->dev, data8[i]); + } + + *cnt = length; + + sh_uart->handler(SHELL_TRANSPORT_EVT_TX_RDY, sh_uart->context); + + return 0; +} + +static int read(const struct shell_transport *transport, + void *data, size_t length, size_t *cnt) +{ + struct shell_uart *sh_uart = (struct shell_uart *)transport->ctx; + + if (sh_uart->rx_cnt) { + memcpy(data, sh_uart->rx, 1); + sh_uart->rx_cnt = 0; + *cnt = 1; + } else { + *cnt = 0; + } + + return 0; +} + +const struct shell_transport_api shell_uart_transport_api = { + .init = init, + .uninit = uninit, + .enable = enable, + .write = write, + .read = read +}; diff --git a/subsys/shell/shell_utils.c b/subsys/shell/shell_utils.c new file mode 100644 index 000000000000..cb83a9d54aa7 --- /dev/null +++ b/subsys/shell/shell_utils.c @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include "shell_utils.h" +#include + +/* Calculates relative line number of given position in buffer */ +static u32_t line_num_with_buffer_offset_get(struct shell_multiline_cons *cons, + u16_t buffer_pos) +{ + return ((buffer_pos + cons->name_len) / cons->terminal_wid); +} + +/* Calculates column number of given position in buffer */ +static u32_t col_num_with_buffer_offset_get(struct shell_multiline_cons *cons, + u16_t buffer_pos) +{ + /* columns are counted from 1 */ + return (1 + ((buffer_pos + cons->name_len) % cons->terminal_wid)); +} + +s32_t column_span_with_buffer_offsets_get(struct shell_multiline_cons *cons, + u16_t offset1, + u16_t offset2) +{ + return col_num_with_buffer_offset_get(cons, offset2) + - col_num_with_buffer_offset_get(cons, offset1); +} + +s32_t row_span_with_buffer_offsets_get(struct shell_multiline_cons *cons, + u16_t offset1, + u16_t offset2) +{ + return line_num_with_buffer_offset_get(cons, offset2) + - line_num_with_buffer_offset_get(cons, offset1); +} + +void shell_multiline_data_calc(struct shell_multiline_cons *cons, + u16_t buff_pos, u16_t buff_len) +{ + /* Current cursor position in command. + * +1 -> because home position is (1, 1) + */ + cons->cur_x = (buff_pos + cons->name_len) % cons->terminal_wid + 1; + cons->cur_y = (buff_pos + cons->name_len) / cons->terminal_wid + 1; + + /* Extreme position when cursor is at the end of command. */ + cons->cur_y_end = (buff_len + cons->name_len) / cons->terminal_wid + 1; + cons->cur_x_end = (buff_len + cons->name_len) % cons->terminal_wid + 1; +} + +static void make_argv(char **ppcmd, u8_t c, u8_t quote) +{ + char *cmd = *ppcmd; + + while (1) { + c = *cmd; + + if (c == '\0') { + break; + } + + if (!quote) { + switch (c) { + case '\\': + memmove(cmd, cmd + 1, + shell_strlen(cmd)); + cmd += 1; + continue; + + case '\'': + case '\"': + memmove(cmd, cmd + 1, + shell_strlen(cmd)); + quote = c; + continue; + default: + break; + } + } + + if (quote == c) { + memmove(cmd, cmd + 1, shell_strlen(cmd)); + quote = 0; + continue; + } + + if (quote && c == '\\') { + char t = *(cmd + 1); + + if (t == quote) { + memmove(cmd, cmd + 1, + shell_strlen(cmd)); + cmd += 1; + continue; + } + + if (t == '0') { + u8_t i; + u8_t v = 0; + + for (i = 2; i < (2 + 3); i++) { + t = *(cmd + i); + + if (t >= '0' && t <= '7') { + v = (v << 3) | (t - '0'); + } else { + break; + } + } + + if (i > 2) { + memmove(cmd, cmd + (i - 1), + shell_strlen(cmd) - (i - 2)); + *cmd++ = v; + continue; + } + } + + if (t == 'x') { + u8_t i; + u8_t v = 0; + + for (i = 2; i < (2 + 2); i++) { + t = *(cmd + i); + + if (t >= '0' && t <= '9') { + v = (v << 4) | (t - '0'); + } else if ((t >= 'a') && + (t <= 'f')) { + v = (v << 4) | (t - 'a' + 10); + } else if ((t >= 'A') && (t <= 'F')) { + v = (v << 4) | (t - 'A' + 10); + } else { + break; + } + } + + if (i > 2) { + memmove(cmd, cmd + (i - 1), + shell_strlen(cmd) - (i - 2)); + *cmd++ = v; + continue; + } + } + } + + if (!quote && isspace((int) c)) { + break; + } + + cmd += 1; + } + *ppcmd = cmd; +} + + +char shell_make_argv(size_t *argc, char **argv, char *cmd, u8_t max_argc) +{ + char quote = 0; + char c; + + *argc = 0; + do { + c = *cmd; + if (c == '\0') { + break; + } + + if (isspace((int) c)) { + *cmd++ = '\0'; + continue; + } + + argv[(*argc)++] = cmd; + quote = 0; + + make_argv(&cmd, c, quote); + } while (*argc < max_argc); + + argv[*argc] = 0; + + return quote; +} + +void shell_pattern_remove(char *buff, u16_t *buff_len, const char *pattern) +{ + char *pattern_addr = strstr(buff, pattern); + u16_t pattern_len = shell_strlen(pattern); + size_t shift; + + if (!pattern_addr) { + return; + } + + if (pattern_addr > buff) { + if (*(pattern_addr - 1) == ' ') { + pattern_len++; /* space needs to be removed as well */ + pattern_addr--; /* set pointer to space */ + } + } + + shift = shell_strlen(pattern_addr) - pattern_len + 1; /* +1 for EOS */ + *buff_len -= pattern_len; + + memmove(pattern_addr, pattern_addr + pattern_len, shift); +} + +int shell_command_add(char *buff, u16_t *buff_len, + const char *new_cmd, const char *pattern) +{ + u16_t cmd_len = shell_strlen(new_cmd); + char *cmd_source_addr; + u16_t shift; + + /* +1 for space */ + if ((*buff_len + cmd_len + 1) > CONFIG_SHELL_CMD_BUFF_SIZE) { + return -ENOMEM; + } + + cmd_source_addr = strstr(buff, pattern); + + if (!cmd_source_addr) { + return -EINVAL; + } + + shift = shell_strlen(cmd_source_addr); + + /* make place for new command: + 1 for space + 1 for EOS */ + memmove(cmd_source_addr + cmd_len + 1, cmd_source_addr, shift + 1); + memcpy(cmd_source_addr, new_cmd, cmd_len); + cmd_source_addr[cmd_len] = ' '; + + *buff_len += cmd_len + 1; /* + 1 for space */ + + return 0; +} + +void shell_spaces_trim(char *str) +{ + u16_t len = shell_strlen(str); + u16_t shift = 0; + + if (!str) { + return; + } + + for (u16_t i = 0; i < len - 1; i++) { + if (isspace((int)str[i])) { + for (u16_t j = i + 1; j < len; j++) { + if (isspace((int)str[j])) { + shift++; + continue; + } + + if (shift > 0) { + /* +1 for EOS */ + memmove(&str[i + 1], + &str[j], + len - shift + 1); + len -= shift; + shift = 0; + } + + break; + } + } + } +} + +void shell_buffer_trim(char *buff, u16_t *buff_len) +{ + u16_t i = 0; + + /* no command in the buffer */ + if (buff[0] == '\0') { + return; + } + + while (isspace((int) buff[*buff_len - 1])) { + *buff_len -= 1; + if (*buff_len == 0) { + buff[0] = '\0'; + return; + } + } + buff[*buff_len] = '\0'; + + /* Counting whitespace characters starting from beginning of the + * command. + */ + while (isspace((int) buff[i++])) { + if (i == 0) { + buff[0] = '\0'; + return; + } + } + + /* Removing counted whitespace characters. */ + if (--i > 0) { + memmove(buff, buff + i, (*buff_len + 1) - i); /* +1 for '\0' */ + *buff_len = *buff_len - i; + } +} + +u16_t shell_str_similarity_check(const char *str_a, const char *str_b) +{ + u16_t cnt = 0; + + while (str_a[cnt] != '\0') { + if (str_a[cnt] != str_b[cnt]) { + return cnt; + } + + if (++cnt == 0) { + return --cnt; /* too long strings */ + } + } + + return cnt; +} diff --git a/subsys/shell/shell_utils.h b/subsys/shell/shell_utils.h new file mode 100644 index 000000000000..04fae207adfb --- /dev/null +++ b/subsys/shell/shell_utils.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ +#ifndef SHELL_UTILS_H__ +#define SHELL_UTILS_H__ + +#include +#include +#ifdef __cplusplus +extern "C" { +#endif + +#define SHELL_MSG_SPECIFY_SUBCOMMAND "Please specify a subcommand.\r\n" + +#define SHELL_DEFAULT_TERMINAL_WIDTH (80u) /* Default PuTTY width. */ +#define SHELL_DEFAULT_TERMINAL_HEIGHT (24u) /* Default PuTTY height. */ + + + +s32_t row_span_with_buffer_offsets_get(struct shell_multiline_cons *cons, + u16_t offset1, + u16_t offset2); + +s32_t column_span_with_buffer_offsets_get(struct shell_multiline_cons *cons, + u16_t offset1, + u16_t offset2); + +void shell_multiline_data_calc(struct shell_multiline_cons *cons, + u16_t buff_pos, u16_t buff_len); + +static inline size_t shell_strlen(const char *str) +{ + return str == NULL ? 0 : strlen(str); +} + +char shell_make_argv(size_t *argc, char **argv, char *cmd, uint8_t max_argc); + +/** @brief Removes pattern and following space + * + */ +void shell_pattern_remove(char *buff, u16_t *buff_len, const char *pattern); + +int shell_command_add(char *buff, u16_t *buff_len, + const char *new_cmd, const char *pattern); + +void shell_spaces_trim(char *str); + +/** @brief Remove white chars from beginning and end of command buffer. + * + */ +void shell_buffer_trim(char *buff, u16_t *buff_len); + +/* Function checks how many identical characters have two strings starting + * from the first character. + */ +u16_t shell_str_similarity_check(const char *str_a, const char *str_b); + +#ifdef __cplusplus +} +#endif + +#endif /* SHELL_UTILS_H__ */ diff --git a/subsys/shell/shell_vt100.h b/subsys/shell/shell_vt100.h new file mode 100644 index 000000000000..8e4a6c6f6c6a --- /dev/null +++ b/subsys/shell/shell_vt100.h @@ -0,0 +1,589 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SHELL_VT100_H__ +#define SHELL_VT100_H__ + +#define SHELL_VT100_ASCII_ESC (0x1b) +#define SHELL_VT100_ASCII_DEL (0x7F) +#define SHELL_VT100_ASCII_BSPACE (0x08) +#define SHELL_VT100_ASCII_CTRL_A (0x01) +#define SHELL_VT100_ASCII_CTRL_C (0x03) +#define SHELL_VT100_ASCII_CTRL_E (0x05) +#define SHELL_VT100_ASCII_CTRL_L (0x0C) +#define SHELL_VT100_ASCII_CTRL_U (0x15) +#define SHELL_VT100_ASCII_CTRL_W (0x17) + +#define SHELL_VT100_SETNL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', '0', 'h', '\0' \ +} /* Set new line mode */ +#define SHELL_VT100_SETAPPL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '1', 'h', '\0' \ +} /* Set cursor key to application */ +#define SHELL_VT100_SETCOL_132 \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '3', 'h', '\0' \ +} /* Set number of columns to 132 */ +#define SHELL_VT100_SETSMOOTH \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '4', 'h', '\0' \ +} /* Set smooth scrolling */ +#define SHELL_VT100_SETREVSCRN \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '5', 'h', '\0' \ +} /* Set reverse video on screen */ +#define SHELL_VT100_SETORGREL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '6', 'h', '\0' \ +} /* Set origin to relative */ +#define SHELL_VT100_SETWRAP_ON \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '7', 'h', '\0' \ +} /* Set auto-wrap mode */ +#define SHELL_VT100_SETWRAP_OFF \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '7', 'l', '\0' \ +} /* Set auto-wrap mode */ + +#define SHELL_VT100_SETREP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '8', 'h', '\0' \ +} /* Set auto-repeat mode */ +#define SHELL_VT100_SETINTER \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '9', 'h', '\0' \ +} /* Set interlacing mode */ + +#define SHELL_VT100_SETLF \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', '0', 'l', '\0' \ +} /* Set line feed mode */ +#define SHELL_VT100_SETCURSOR \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '1', 'l', '\0' \ +} /* Set cursor key to cursor */ +#define SHELL_VT100_SETVT52 \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '2', 'l', '\0' \ +} /* Set VT52 (versus ANSI) */ +#define SHELL_VT100_SETCOL_80 \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '3', 'l', '\0' \ +} /* Set number of columns to 80 */ +#define SHELL_VT100_SETJUMP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '4', 'l', '\0' \ +} /* Set jump scrolling */ +#define SHELL_VT100_SETNORMSCRN \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '5', 'l', '\0' \ +} /* Set normal video on screen */ +#define SHELL_VT100_SETORGABS \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '6', 'l', '\0' \ +} /* Set origin to absolute */ +#define SHELL_VT100_RESETWRAP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '7', 'l', '\0' \ +} /* Reset auto-wrap mode */ +#define SHELL_VT100_RESETREP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '8', 'l', '\0' \ +} /* Reset auto-repeat mode */ +#define SHELL_VT100_RESETINTER \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '9', 'l', '\0' \ +} /* Reset interlacing mode */ + +#define SHELL_VT100_ALTKEYPAD \ +{ \ + SHELL_VT100_ASCII_ESC, '=', '\0' \ +} /* Set alternate keypad mode */ +#define SHELL_VT100_NUMKEYPAD \ +{ \ + SHELL_VT100_ASCII_ESC, '>', '\0' \ +} /* Set numeric keypad mode */ + +#define SHELL_VT100_SETUKG0 \ +{ \ + SHELL_VT100_ASCII_ESC, '(', 'A', '\0' \ +} /* Set United Kingdom G0 character set */ +#define SHELL_VT100_SETUKG1 \ +{ \ + SHELL_VT100_ASCII_ESC, ')', 'A', '\0' \ +} /* Set United Kingdom G1 character set */ +#define SHELL_VT100_SETUSG0 \ +{ \ + SHELL_VT100_ASCII_ESC, '(', 'B', '\0' \ +} /* Set United States G0 character set */ +#define SHELL_VT100_SETUSG1 \ +{ \ + SHELL_VT100_ASCII_ESC, ')', 'B', '\0' \ +} /* Set United States G1 character set */ +#define SHELL_VT100_SETSPECG0 \ +{ \ + SHELL_VT100_ASCII_ESC, '(', '0', '\0' \ +} /* Set G0 special chars. & line set */ +#define SHELL_VT100_SETSPECG1 \ +{ \ + SHELL_VT100_ASCII_ESC, ')', '0', '\0' \ +} /* Set G1 special chars. & line set */ +#define SHELL_VT100_SETALTG0 \ +{ \ + SHELL_VT100_ASCII_ESC, '(', '1', '\0' \ +} /* Set G0 alternate character ROM */ +#define SHELL_VT100_SETALTG1 \ +{ \ + SHELL_VT100_ASCII_ESC, ')', '1', '\0' \ +} /* Set G1 alternate character ROM */ +#define SHELL_VT100_SETALTSPECG0 \ +{ \ + SHELL_VT100_ASCII_ESC, '(', '2', '\0' \ +} /* Set G0 alt char ROM and spec. graphics */ +#define SHELL_VT100_SETALTSPECG1 \ +{ \ + SHELL_VT100_ASCII_ESC, ')', '2', '\0' \ +} /* Set G1 alt char ROM and spec. graphics */ + +#define SHELL_VT100_SETSS2 \ +{ \ + SHELL_VT100_ASCII_ESC, 'N', '\0' \ +} /* Set single shift 2 */ +#define SHELL_VT100_SETSS3 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', '\0' \ +} /* Set single shift 3 */ + +#define SHELL_VT100_MODESOFF \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'm', '\0' \ +} /* Turn off character attributes */ +#define SHELL_VT100_MODESOFF_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '0', 'm', '\0' \ +} /* Turn off character attributes */ +#define SHELL_VT100_BOLD \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '1', 'm', '\0' \ +} /* Turn bold mode on */ +#define SHELL_VT100_LOWINT \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', 'm', '\0' \ +} /* Turn low intensity mode on */ +#define SHELL_VT100_UNDERLINE \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '4', 'm', '\0' \ +} /* Turn underline mode on */ +#define SHELL_VT100_BLINK \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '5', 'm', '\0' \ +} /* Turn blinking mode on */ +#define SHELL_VT100_REVERSE \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '7', 'm', '\0' \ +} /* Turn reverse video on */ +#define SHELL_VT100_INVISIBLE \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '8', 'm', '\0' \ +} /* Turn invisible text mode on */ + +#define SHELL_VT100_SETWIN(t, b) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (t), ';', (b), 'r', '\0' \ +} /* Set top and bottom line#s of a window */ + +#define SHELL_VT100_CURSORUP(n) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (n), 'A', '\0' \ +} /* Move cursor up n lines */ +#define SHELL_VT100_CURSORDN(n) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (n), 'B', '\0' \ +} /* Move cursor down n lines */ +#define SHELL_VT100_CURSORRT(n) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (n), 'C', '\0' \ +} /* Move cursor right n lines */ +#define SHELL_VT100_CURSORLF(n) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (n), 'D', '\0' \ +} /* Move cursor left n lines */ +#define SHELL_VT100_CURSORHOME \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'H', '\0' \ +} /* Move cursor to upper left corner */ +#define SHELL_VT100_CURSORHOME_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', ';', 'H', '\0' \ +} /* Move cursor to upper left corner */ +#define SHELL_VT100_CURSORPOS(v, h) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (v), ';', (h), 'H', '\0' \ +} /* Move cursor to screen location v,h */ + +#define SHELL_VT100_HVHOME \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'f', '\0' \ +} /* Move cursor to upper left corner */ +#define SHELL_VT100_HVHOME_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', ';', 'f', '\0' \ +} /* Move cursor to upper left corner */ +#define SHELL_VT100_HVPOS(v, h) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', (v), ';', (h), 'f', '\0' \ +} /* Move cursor to screen location v,h */ +#define SHELL_VT100_INDEX \ +{ \ + SHELL_VT100_ASCII_ESC, 'D', '\0' \ +} /* Move/scroll window up one line */ +#define SHELL_VT100_REVINDEX \ +{ \ + SHELL_VT100_ASCII_ESC, 'M', '\0' \ +} /* Move/scroll window down one line */ +#define SHELL_VT100_NEXTLINE \ +{ \ + SHELL_VT100_ASCII_ESC, 'E', '\0' \ +} /* Move to next line */ +#define SHELL_VT100_SAVECURSOR \ +{ \ + SHELL_VT100_ASCII_ESC, '7', '\0' \ +} /* Save cursor position and attributes */ +#define SHELL_VT100_RESTORECURSOR \ +{ \ + SHELL_VT100_ASCII_ESC, '8', '\0' \ +} /* Restore cursor position and attribute */ + +#define SHELL_VT100_TABSET \ +{ \ + SHELL_VT100_ASCII_ESC, 'H', '\0' \ +} /* Set a tab at the current column */ +#define SHELL_VT100_TABCLR \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'g', '\0' \ +} /* Clear a tab at the current column */ +#define SHELL_VT100_TABCLR_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '0', 'g', '\0' \ +} /* Clear a tab at the current column */ +#define SHELL_VT100_TABCLRALL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '3', 'g', '\0' \ +} /* Clear all tabs */ + +#define SHELL_VT100_DHTOP \ +{ \ + SHELL_VT100_ASCII_ESC, '#', '3', '\0' \ +} /* Double-height letters, top half */ +#define SHELL_VT100_DHBOT \ +{ \ + SHELL_VT100_ASCII_ESC, '#', '4', '\0' \ +} /* Double-height letters, bottom hal */ +#define SHELL_VT100_SWSH \ +{ \ + SHELL_VT100_ASCII_ESC, '#', '5', '\0' \ +} /* Single width, single height letters */ +#define SHELL_VT100_DWSH \ +{ \ + SHELL_VT100_ASCII_ESC, '#', '6', '\0' \ +} /* Double width, single height letters */ + +#define SHELL_VT100_CLEAREOL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'K', '\0' \ +} /* Clear line from cursor right */ +#define SHELL_VT100_CLEAREOL_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '0', 'K', '\0' \ +} /* Clear line from cursor right */ +#define SHELL_VT100_CLEARBOL \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '1', 'K', '\0' \ +} /* Clear line from cursor left */ +#define SHELL_VT100_CLEARLINE \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', 'K', '\0' \ +} /* Clear entire line */ + +#define SHELL_VT100_CLEAREOS \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'J', '\0' \ +} /* Clear screen from cursor down */ +#define SHELL_VT100_CLEAREOS_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '0', 'J', '\0' \ +} /* Clear screen from cursor down */ +#define SHELL_VT100_CLEARBOS \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '1', 'J', '\0' \ +} /* Clear screen from cursor up */ +#define SHELL_VT100_CLEARSCREEN \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', 'J', '\0' \ +} /* Clear entire screen */ + +#define SHELL_VT100_DEVSTAT \ +{ \ + SHELL_VT100_ASCII_ESC, '5', 'n', '\0' \ +} /* Device status report */ +#define SHELL_VT100_TERMOK \ +{ \ + SHELL_VT100_ASCII_ESC, '0', 'n', '\0' \ +} /* Response: terminal is OK */ +#define SHELL_VT100_TERMNOK \ +{ \ + SHELL_VT100_ASCII_ESC, '3', 'n', '\0' \ +} /* Response: terminal is not OK */ + +#define SHELL_VT100_GETCURSOR \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '6', 'n', '\0' \ +} /* Get cursor position */ +#define SHELL_VT100_CURSORPOSAT \ +{ \ + SHELL_VT100_ASCII_ESC, (v), ';', (h), 'R', '\0' \ +} /* Response: cursor is at v,h */ + +#define SHELL_VT100_IDENT \ +{ \ + SHELL_VT100_ASCII_ESC, '[', 'c', '\0' \ +} /* Identify what terminal type */ +#define SHELL_VT100_IDENT_ \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '0', 'c', '\0' \ +} /* Identify what terminal type */ +#define SHELL_VT100_GETTYPE \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '?', '1', ';', (n), '0', 'c', '\0' \ +} /* Response: terminal type code n */ + +#define SHELL_VT100_RESET \ +{ \ + SHELL_VT100_ASCII_ESC, 'c', '\0' \ +} /*Reset terminal to initial state */ + +#define SHELL_VT100_ALIGN \ +{ \ + SHELL_VT100_ASCII_ESC, '#', '8', '\0' \ +} /* Screen alignment display */ +#define SHELL_VT100_TESTPU \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', ';', '1', 'y', '\0' \ +} /* Confidence power up test */ +#define SHELL_VT100_TESTLB \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', ';', '2', 'y', '\0' \ +} /* Confidence loopback test */ +#define SHELL_VT100_TESTPUREP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', ';', '9', 'y', '\0' \ +} /* Repeat power up test */ +#define SHELL_VT100_TESTLBREP \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '2', ';', '1', '0', 'y', '\0' \ +} /* Repeat loopback test */ + +#define SHELL_VT100_LEDSOFF \ +{ \ +SHELL_VT100_ASCII_ESC, '[', '0', 'q', '\0' \ +} /* Turn off all four leds */ +#define SHELL_VT100_LED1 \ +{ \ +SHELL_VT100_ASCII_ESC, '[', '1', 'q', '\0' \ +} /* Turn on LED #1 */ +#define SHELL_VT100_LED2 \ +{ \ +SHELL_VT100_ASCII_ESC, '[', '2', 'q', '\0' \ +} /* Turn on LED #2 */ +#define SHELL_VT100_LED3 \ +{ \ +SHELL_VT100_ASCII_ESC, '[', '3', 'q', '\0' \ +} /* Turn on LED #3 */ +#define SHELL_VT100_LED4 \ +{ \ +SHELL_VT100_ASCII_ESC, '[', '4', 'q', '\0' \ +} /* Turn on LED #4 */ + +/* Function Keys */ + +#define SHELL_VT100_PF1 \ +{ \ +SHELL_VT100_ASCII_ESC, 'O', 'P', '\0' \ +} +#define SHELL_VT100_PF2 \ +{ \ +SHELL_VT100_ASCII_ESC, 'O', 'Q', '\0' \ +} + +#define SHELL_VT100_PF3 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'R', '\0' \ +} +#define SHELL_VT100_PF4 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'S', '\0' \ +} + +/* Arrow keys */ + +#define SHELL_VT100_UP_RESET \ +{ \ + SHELL_VT100_ASCII_ESC, 'A', '\0' \ +} +#define SHELL_VT100_UP_SET \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'A', '\0' \ +} +#define SHELL_VT100_DOWN_RESET \ +{ \ + SHELL_VT100_ASCII_ESC, 'B', '\0' \ +} +#define SHELL_VT100_DOWN_SET \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'B', '\0' \ +} +#define SHELL_VT100_RIGHT_RESET \ +{ \ + SHELL_VT100_ASCII_ESC, 'C', '\0' \ +} +#define SHELL_VT100_RIGHT_SET \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'C', '\0' \ +} +#define SHELL_VT100_LEFT_RESET \ +{ \ + SHELL_VT100_ASCII_ESC, 'D', '\0' \ +} +#define SHELL_VT100_LEFT_SET \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'D', '\0' \ +} + +/* Numeric Keypad Keys */ +#define SHELL_VT100_NUMERIC_0 \ +{ \ + '0', '\0' \ +} +#define SHELL_VT100_ALT_0 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'p', '\0' \ +} +#define SHELL_VT100_NUMERIC_1 \ +{ \ + '1', '\0' \ +} +#define SHELL_VT100_ALT_1 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'q', '\0' \ +} +#define SHELL_VT100_NUMERIC_2 \ +{ \ + '2', '\0' \ +} +#define SHELL_VT100_ALT_2 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'r', '\0' \ +} +#define SHELL_VT100_NUMERIC_3 \ +{ \ + '3', '\0' \ +} +#define SHELL_VT100_ALT_3 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 's', '\0' \ +} +#define SHELL_VT100_NUMERIC_4 \ +{ \ + '4', '\0' \ +} +#define SHELL_VT100_ALT_4 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 't', '\0' \ +} +#define SHELL_VT100_NUMERIC_5 \ +{ \ + '5', '\0' \ +} +#define SHELL_VT100_ALT_5 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'u', '\0' \ +} +#define SHELL_VT100_NUMERIC_6 \ +{ \ + '6', '\0' \ +} +#define SHELL_VT100_ALT_6 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'v', '\0' \ +} +#define SHELL_VT100_NUMERIC_7 \ +{ \ + '7', '\0' \ +} +#define SHELL_VT100_ALT_7 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'w', '\0' \ +} +#define SHELL_VT100_NUMERIC_8 \ +{ \ + '8', '\0' \ +} +#define SHELL_VT100_ALT_8 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'x', '\0' \ +} +#define SHELL_VT100_NUMERIC_9 \ +{ \ + '9', '\0' \ +} +#define SHELL_VT100_ALT_9 \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'y' \ +} +#define SHELL_VT100_NUMERIC_MINUS \ +{ \ + '-', '\0' \ +} +#define SHELL_VT100_ALT_MINUS \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'm', '\0' \ +} +#define SHELL_VT100_NUMERIC_COMMA \ +{ \ + ',', '\0' \ +} +#define SHELL_VT100_ALT_COMMA \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'l', '\0' \ +} +#define SHELL_VT100_NUMERIC_ENTER \ +{ \ + ASCII_CR \ +} +#define SHELL_VT100_NUMERIC_PERIOD \ +{ \ + '.', '\0' \ +} +#define SHELL_VT100_ALT_PERIOD \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'n', '\0' \ +} +#define SHELL_VT100_ALT_ENTER \ +{ \ + SHELL_VT100_ASCII_ESC, 'O', 'M', '\0' \ +} +#define SHELL_VT100_BGCOLOR(__col) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '4', '0' + (__col), 'm', '\0' \ +} +#define SHELL_VT100_COLOR(__col) \ +{ \ + SHELL_VT100_ASCII_ESC, '[', '1', ';', '3', '0' + (__col), 'm', '\0' \ +} + +#endif /* SHELL_VT100_H__ */ diff --git a/subsys/shell/shell_wildcard.c b/subsys/shell/shell_wildcard.c new file mode 100644 index 000000000000..685194a351f2 --- /dev/null +++ b/subsys/shell/shell_wildcard.c @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "shell_wildcard.h" +#include "shell_utils.h" + +static void subcmd_get(const struct shell_cmd_entry *cmd, + size_t idx, const struct shell_static_entry **entry, + struct shell_static_entry *d_entry) +{ + assert(entry != NULL); + assert(st_entry != NULL); + + if (cmd == NULL) { + *entry = NULL; + return; + } + + if (cmd->is_dynamic) { + cmd->u.dynamic_get(idx, d_entry); + *entry = (d_entry->syntax != NULL) ? d_entry : NULL; + } else { + *entry = (cmd->u.entry[idx].syntax != NULL) ? + &cmd->u.entry[idx] : NULL; + } +} + +static enum shell_wildcard_status command_add(char *buff, u16_t *buff_len, + char const *cmd, + char const *pattern) +{ + u16_t cmd_len = shell_strlen(cmd); + char *completion_addr; + u16_t shift; + + /* +1 for space */ + if ((*buff_len + cmd_len + 1) > CONFIG_SHELL_CMD_BUFF_SIZE) { + return SHELL_WILDCARD_CMD_MISSING_SPACE; + } + + completion_addr = strstr(buff, pattern); + + if (!completion_addr) { + return SHELL_WILDCARD_CMD_NO_MATCH_FOUND; + } + + shift = shell_strlen(completion_addr); + + /* make place for new command: + 1 for space + 1 for EOS */ + memmove(completion_addr + cmd_len + 1, completion_addr, shift + 1); + memcpy(completion_addr, cmd, cmd_len); + /* adding space to not brake next command in the buffer */ + completion_addr[cmd_len] = ' '; + + *buff_len += cmd_len + 1; /* + 1 for space */ + + return SHELL_WILDCARD_CMD_ADDED; +} + +/** + * @internal @brief Function for searching and adding commands to the temporary + * shell buffer matching to wildcard pattern. + * + * Function will search commands tree for commands matching wildcard pattern + * stored in argv[cmd_lvl]. When match is found wildcard pattern will be + * replaced by matching commands. If there is no space in the buffer to add all + * matching commands function will add as many as possible. Next it will + * continue to search for next wildcard pattern and it will try to add matching + * commands. + * + * + * This function is internal to shell module and shall be not called directly. + * + * @param[in/out] shell Pointer to the CLI instance. + * @param[in] cmd Pointer to command which will be processed + * @param[in] pattern Pointer to wildcard pattern. + * + * @retval WILDCARD_CMD_ADDED All matching commands added to the buffer. + * @retval WILDCARD_CMD_ADDED_MISSING_SPACE Not all matching commands added + * because CONFIG_SHELL_CMD_BUFF_SIZE + * is too small. + * @retval WILDCARD_CMD_NO_MATCH_FOUND No matching command found. + */ +static enum shell_wildcard_status commands_expand(const struct shell *shell, + const struct shell_cmd_entry *cmd, + const char *pattern) +{ + enum shell_wildcard_status ret_val = SHELL_WILDCARD_CMD_NO_MATCH_FOUND; + struct shell_static_entry const *p_static_entry = NULL; + struct shell_static_entry static_entry; + size_t cmd_idx = 0; + size_t cnt = 0; + + do { + subcmd_get(cmd, cmd_idx++, &p_static_entry, &static_entry); + + if (!p_static_entry) { + break; + } + + if (fnmatch(pattern, p_static_entry->syntax, 0) == 0) { + ret_val = command_add(shell->ctx->temp_buff, + &shell->ctx->cmd_tmp_buff_len, + p_static_entry->syntax, pattern); + if (ret_val == SHELL_WILDCARD_CMD_MISSING_SPACE) { + shell_fprintf(shell, + SHELL_WARNING, + "Command buffer is too short to" + " expand all commands matching" + " wildcard pattern: %s\r\n", + pattern); + break; + } else if (ret_val != SHELL_WILDCARD_CMD_ADDED) { + break; + } + cnt++; + } + } while (cmd_idx); + + if (cnt > 0) { + shell_pattern_remove(shell->ctx->temp_buff, + &shell->ctx->cmd_tmp_buff_len, pattern); + } + + return ret_val; +} + +bool shell_wildcard_character_exist(const char *str) +{ + size_t str_len = shell_strlen(str); + + for (size_t i = 0; i < str_len; i++) { + if ((str[i] == '?') || (str[i] == '*')) { + return true; + } + } + + return false; +} + +void shell_wildcard_prepare(const struct shell *shell) +{ + /* Wildcard can be correctly handled under following conditions: + * - wildcard command does not have a handler + * - wildcard command is on the deepest commands level + * - other commands on the same level as wildcard command shall also not + * have a handler + * + * Algorithm: + * 1. Command buffer: ctx->cmd_buff is copied to temporary buffer: + * ctx->temp_buff. + * 2. Algorithm goes through command buffer to find handlers and + * subcommands. + * 3. If algorithm will find a wildcard character it switches to + * Temporary buffer. + * 4. In the Temporary buffer command containing wildcard character is + * replaced by matching command(s). + * 5. Algorithm switch back to Command buffer and analyzes next command. + * 6. When all arguments are analyzed from Command buffer, Temporary + * buffer with all expanded commands is copied to Command buffer. + * 7. Deepest found handler is executed and all lower level commands, + * including expanded commands, are passed as arguments. + */ + + memset(shell->ctx->temp_buff, 0, sizeof(shell->ctx->temp_buff)); + memcpy(shell->ctx->temp_buff, + shell->ctx->cmd_buff, + shell->ctx->cmd_buff_len); + + /* Function shell_spaces_trim must be used instead of shell_make_argv. + * At this point it is important to keep temp_buff as one string. + * It will allow to find wildcard commands easily with strstr function. + */ + shell_spaces_trim(shell->ctx->temp_buff); + + /* +1 for EOS*/ + shell->ctx->cmd_tmp_buff_len = shell_strlen(shell->ctx->temp_buff) + 1; +} + + +enum shell_wildcard_status shell_wildcard_process(const struct shell *shell, + const struct shell_cmd_entry *cmd, + const char *pattern) +{ + enum shell_wildcard_status ret_val = SHELL_WILDCARD_NOT_FOUND; + + if (cmd == NULL) { + return ret_val; + } + + if (!shell_wildcard_character_exist(pattern)) { + return ret_val; + } + + /* Function will search commands tree for commands matching wildcard + * pattern stored in argv[cmd_lvl]. When match is found wildcard pattern + * will be replaced by matching commands. If there is no space in the + * buffer to add all matching commands function will add as many as + * possible. Next it will continue to search for next wildcard pattern + * and it will try to add matching commands. + */ + ret_val = commands_expand(shell, cmd, pattern); + + return ret_val; +} + +void shell_wildcard_finalize(const struct shell *shell) +{ + memcpy(shell->ctx->cmd_buff, + shell->ctx->temp_buff, + shell->ctx->cmd_tmp_buff_len); + shell->ctx->cmd_buff_len = shell->ctx->cmd_tmp_buff_len; +} diff --git a/subsys/shell/shell_wildcard.h b/subsys/shell/shell_wildcard.h new file mode 100644 index 000000000000..47d776c3e717 --- /dev/null +++ b/subsys/shell/shell_wildcard.h @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2018 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef SHELL_SHELL_WILDCARDS_H__ +#define SHELL_SHELL_WILDCARDS_H__ + +#include + +enum shell_wildcard_status { + SHELL_WILDCARD_CMD_ADDED, + SHELL_WILDCARD_CMD_MISSING_SPACE, + SHELL_WILDCARD_CMD_NO_MATCH_FOUND, /* no matching command */ + SHELL_WILDCARD_NOT_FOUND /* wildcard character not found */ +}; + +/* Function initializing wildcard expansion procedure. + * + * @param[in] shell Pointer to the shell instance. + */ +void shell_wildcard_prepare(const struct shell *shell); + +/* Function returns true if string contains wildcard character: '?' or '*' + * + * @param[in] str Pointer to string. + * + * @retval true wildcard character found + * @retval false wildcard character not found + */ +bool shell_wildcard_character_exist(const char *str); + +/* Function expands wildcards in the shell temporary buffer + * + * @brief Function evaluates one command. If command contains wildcard patter, + * function will expand this command with all commands matching wildcard + * pattern. + * + * Function will print a help string with: the currently entered command, its + * options,and subcommands (if they exist). + * + * @param[in] shell Pointer to the shell instance. + * @param[in] cmd Pointer to command which will be processed. + * @param[in] pattern Pointer to wildcard pattern. + */ +enum shell_wildcard_status shell_wildcard_process(const struct shell *shell, + const struct shell_cmd_entry *cmd, + const char *pattern); + +/* Function finalizing wildcard expansion procedure. + * + * @param[in] shell Pointer to the shell instance. + */ +void shell_wildcard_finalize(const struct shell *shell); + + +#endif /* SHELL_SHELL_WILDCARDS_H__ */ diff --git a/tests/bluetooth/mesh_shell/src/main.c b/tests/bluetooth/mesh_shell/src/main.c index 3eb50cfd155e..0ccb669f9017 100644 --- a/tests/bluetooth/mesh_shell/src/main.c +++ b/tests/bluetooth/mesh_shell/src/main.c @@ -11,7 +11,7 @@ #include #include -#include +#include void main(void) { diff --git a/tests/bluetooth/shell/src/main.c b/tests/bluetooth/shell/src/main.c index 93c9eb8b6b3c..7a68fb1354dd 100644 --- a/tests/bluetooth/shell/src/main.c +++ b/tests/bluetooth/shell/src/main.c @@ -21,7 +21,7 @@ #include #include -#include +#include #include diff --git a/tests/include/tc_util.h b/tests/include/tc_util.h index 835ad8db9fb1..de34e1622e03 100644 --- a/tests/include/tc_util.h +++ b/tests/include/tc_util.h @@ -12,7 +12,7 @@ #include #include -#include +#include #if defined(CONFIG_STDOUT_CONSOLE) #include diff --git a/tests/shell/src/main.c b/tests/shell/src/main.c index ddc4a5dbba03..9c859ad265ca 100644 --- a/tests/shell/src/main.c +++ b/tests/shell/src/main.c @@ -12,7 +12,7 @@ #include #include -#include +#include static void test_shell_exec(const char *line, int result) { diff --git a/tests/subsys/fs/multi-fs/src/test_fs_shell.c b/tests/subsys/fs/multi-fs/src/test_fs_shell.c index fb13688e820c..b82e930195f7 100644 --- a/tests/subsys/fs/multi-fs/src/test_fs_shell.c +++ b/tests/subsys/fs/multi-fs/src/test_fs_shell.c @@ -13,7 +13,7 @@ #include #include "test_fs_shell.h" -#include +#include static void test_shell_exec(const char *line, int result) {