openocd: fix SPDX tag format for files .c
[fw/openocd] / src / helper / command.c
index 3abfdd67385debac2b4d4bc691e6a7caf918486b..6898e2d7c644b073778f3f26a6c28f60c51859bb 100644 (file)
@@ -1,3 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
 /***************************************************************************
  *   Copyright (C) 2005 by Dominic Rath                                    *
  *   Dominic.Rath@gmx.de                                                   *
  *                                                                         *
  *   part of this file is taken from libcli (libcli.sourceforge.net)       *
  *   Copyright (C) David Parrish (david@dparrish.com)                      *
- *                                                                         *
- *   This program is free software; you can redistribute it and/or modify  *
- *   it under the terms of the GNU General Public License as published by  *
- *   the Free Software Foundation; either version 2 of the License, or     *
- *   (at your option) any later version.                                   *
- *                                                                         *
- *   This program is distributed in the hope that it will be useful,       *
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
- *   GNU General Public License for more details.                          *
- *                                                                         *
- *   You should have received a copy of the GNU General Public License     *
- *   along with this program; if not, write to the                         *
- *   Free Software Foundation, Inc.,                                       *
- *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
  ***************************************************************************/
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
-#if !BUILD_ECOSBOARD
-/* see Embedder-HOWTO.txt in Jim Tcl project hosted on BerliOS*/
+/* see Embedded-HOWTO.txt in Jim Tcl project hosted on BerliOS*/
 #define JIM_EMBEDDED
-#endif
 
-// @todo the inclusion of target.h here is a layering violation
-#include "target.h"
+/* @todo the inclusion of target.h here is a layering violation */
+#include <jtag/jtag.h>
+#include <target/target.h>
 #include "command.h"
 #include "configuration.h"
 #include "log.h"
 #include "time_support.h"
 #include "jim-eventloop.h"
 
+/* nice short description of source file */
+#define __THIS__FILE__ "command.c"
 
-int fast_and_dangerous = 0;
-Jim_Interp *interp = NULL;
-
-static int run_command(struct command_context *context,
-               struct command *c, const char *words[], unsigned num_words);
-
-static void tcl_output(void *privData, const char *file, unsigned line,
-               const char *function, const char *string)
+struct log_capture_state {
+       Jim_Interp *interp;
+       Jim_Obj *output;
+};
+
+static int unregister_command(struct command_context *context,
+       const char *cmd_prefix, const char *name);
+static int jim_command_dispatch(Jim_Interp *interp, int argc, Jim_Obj * const *argv);
+static int help_add_command(struct command_context *cmd_ctx,
+       const char *cmd_name, const char *help_text, const char *usage_text);
+static int help_del_command(struct command_context *cmd_ctx, const char *cmd_name);
+
+/* set of functions to wrap jimtcl internal data */
+static inline bool jimcmd_is_proc(Jim_Cmd *cmd)
 {
-       Jim_Obj *tclOutput = (Jim_Obj *)privData;
-       Jim_AppendString(interp, tclOutput, string, strlen(string));
+       return cmd->isproc;
 }
 
-extern struct command_context *global_cmd_ctx;
-
-void script_debug(Jim_Interp *interp, const char *name,
-               unsigned argc, Jim_Obj *const *argv)
+bool jimcmd_is_oocd_command(Jim_Cmd *cmd)
 {
-       LOG_DEBUG("command - %s", name);
-       for (unsigned i = 0; i < argc; i++)
-       {
-               int len;
-               const char *w = Jim_GetString(argv[i], &len);
-
-               /* end of line comment? */
-               if (*w == '#')
-                       break;
+       return !cmd->isproc && cmd->u.native.cmdProc == jim_command_dispatch;
+}
 
-               LOG_DEBUG("%s - argv[%d]=%s", name, i, w);
-       }
+void *jimcmd_privdata(Jim_Cmd *cmd)
+{
+       return cmd->isproc ? NULL : cmd->u.native.privData;
 }
 
-static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+static void tcl_output(void *privData, const char *file, unsigned line,
+       const char *function, const char *string)
 {
-       /* the private data is stashed in the interp structure */
-       struct command *c;
-       struct command_context *context;
-       int retval;
-       int i;
-       int nwords;
-       char **words;
+       struct log_capture_state *state = privData;
+       Jim_AppendString(state->interp, state->output, string, strlen(string));
+}
 
-       /* DANGER!!!! be careful what we invoke here, since interp->cmdPrivData might
-        * get overwritten by running other Jim commands! Treat it as an
-        * emphemeral global variable that is used in lieu of an argument
-        * to the fn and fish it out manually.
-        */
-       c = interp->cmdPrivData;
-       if (c == NULL)
-       {
-               LOG_ERROR("BUG: interp->cmdPrivData == NULL");
-               return JIM_ERR;
-       }
-       target_call_timer_callbacks_now();
-       LOG_USER_N("%s", ""); /* Keep GDB connection alive*/
+static struct log_capture_state *command_log_capture_start(Jim_Interp *interp)
+{
+       /* capture log output and return it. A garbage collect can
+        * happen, so we need a reference count to this object */
+       Jim_Obj *jim_output = Jim_NewStringObj(interp, "", 0);
+       if (!jim_output)
+               return NULL;
 
-       script_debug(interp, c->name, argc, argv);
+       Jim_IncrRefCount(jim_output);
 
-       words = malloc(sizeof(char *) * (argc + 1));
-       words[0] = c->name;
-       for (i = 0; i < argc; i++)
-       {
-               int len;
-               const char *w = Jim_GetString(argv[i], &len);
-               if (*w=='#')
-               {
-                       /* hit an end of line comment */
-                       break;
-               }
-               words[i + 1] = strdup(w);
-               if (words[i + 1] == NULL)
-               {
-                       int j;
-                       for (j = 0; j < i; j++)
-                               free(words[j + 1]);
-                       free(words);
-                       return JIM_ERR;
-               }
+       struct log_capture_state *state = malloc(sizeof(*state));
+       if (!state) {
+               LOG_ERROR("Out of memory");
+               Jim_DecrRefCount(interp, jim_output);
+               return NULL;
        }
-       nwords = i;
 
-       /* grab the command context from the associated data */
-       context = Jim_GetAssocData(interp, "context");
-       if (context == NULL)
-       {
-               /* Tcl can invoke commands directly instead of via command_run_line(). This would
-                * happen when the Jim Tcl interpreter is provided by eCos.
-                */
-               context = global_cmd_ctx;
-       }
+       state->interp = interp;
+       state->output = jim_output;
 
-       /* capture log output and return it */
-       Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
-       /* a garbage collect can happen, so we need a reference count to this object */
-       Jim_IncrRefCount(tclOutput);
+       log_add_callback(tcl_output, state);
 
-       log_add_callback(tcl_output, tclOutput);
+       return state;
+}
 
-       // turn words[0] into args[-1] with this cast
-       retval = run_command(context, c, (const char **)words + 1, nwords);
+/* Classic openocd commands provide progress output which we
+ * will capture and return as a Tcl return value.
+ *
+ * However, if a non-openocd command has been invoked, then it
+ * makes sense to return the tcl return value from that command.
+ *
+ * The tcl return value is empty for openocd commands that provide
+ * progress output.
+ *
+ * Therefore we set the tcl return value only if we actually
+ * captured output.
+ */
+static void command_log_capture_finish(struct log_capture_state *state)
+{
+       if (!state)
+               return;
 
-       log_remove_callback(tcl_output, tclOutput);
+       log_remove_callback(tcl_output, state);
 
-       /* We dump output into this local variable */
-       Jim_SetResult(interp, tclOutput);
-       Jim_DecrRefCount(interp, tclOutput);
+       int length;
+       Jim_GetString(state->output, &length);
 
-       for (i = 0; i < nwords; i++)
-               free(words[i + 1]);
-       free(words);
+       if (length > 0)
+               Jim_SetResult(state->interp, state->output);
+       else {
+               /* No output captured, use tcl return value (which could
+                * be empty too). */
+       }
+       Jim_DecrRefCount(state->interp, state->output);
 
+       free(state);
+}
+
+static int command_retval_set(Jim_Interp *interp, int retval)
+{
        int *return_retval = Jim_GetAssocData(interp, "retval");
-       if (return_retval != NULL)
-       {
+       if (return_retval)
                *return_retval = retval;
-       }
 
-       return (retval == ERROR_OK)?JIM_OK:JIM_ERR;
+       return (retval == ERROR_OK) ? JIM_OK : retval;
 }
 
-static Jim_Obj *command_name_list(struct command *c)
+extern struct command_context *global_cmd_ctx;
+
+/* dump a single line to the log for the command.
+ * Do nothing in case we are not at debug level 3 */
+static void script_debug(Jim_Interp *interp, unsigned int argc, Jim_Obj * const *argv)
 {
-       Jim_Obj *cmd_list = c->parent ?
-                       command_name_list(c->parent) :
-                       Jim_NewListObj(interp, NULL, 0);
-       Jim_ListAppendElement(interp, cmd_list,
-                       Jim_NewStringObj(interp, c->name, -1));
+       if (debug_level < LOG_LVL_DEBUG)
+               return;
 
-       return cmd_list;
+       char *dbg = alloc_printf("command -");
+       for (unsigned i = 0; i < argc; i++) {
+               int len;
+               const char *w = Jim_GetString(argv[i], &len);
+               char *t = alloc_printf("%s %s", dbg, w);
+               free(dbg);
+               dbg = t;
+       }
+       LOG_DEBUG("%s", dbg);
+       free(dbg);
 }
 
-static void command_helptext_add(Jim_Obj *cmd_list, const char *help)
+static void script_command_args_free(char **words, unsigned nwords)
 {
-       Jim_Obj *cmd_entry = Jim_NewListObj(interp, NULL, 0);
-       Jim_ListAppendElement(interp, cmd_entry, cmd_list);
-       Jim_ListAppendElement(interp, cmd_entry,
-                       Jim_NewStringObj(interp, help ? : "", -1));
-
-       /* accumulate help text in Tcl helptext list.  */
-       Jim_Obj *helptext = Jim_GetGlobalVariableStr(interp,
-                       "ocd_helptext", JIM_ERRMSG);
-       if (Jim_IsShared(helptext))
-               helptext = Jim_DuplicateObj(interp, helptext);
-       Jim_ListAppendElement(interp, helptext, cmd_entry);
+       for (unsigned i = 0; i < nwords; i++)
+               free(words[i]);
+       free(words);
 }
 
-/* nice short description of source file */
-#define __THIS__FILE__ "command.c"
+static char **script_command_args_alloc(
+       unsigned argc, Jim_Obj * const *argv, unsigned *nwords)
+{
+       char **words = malloc(argc * sizeof(char *));
+       if (!words)
+               return NULL;
 
-/**
- * Find a command by name from a list of commands.
- * @returns The named command if found, or NULL.
- */
-static struct command *command_find(struct command **head, const char *name)
+       unsigned i;
+       for (i = 0; i < argc; i++) {
+               int len;
+               const char *w = Jim_GetString(argv[i], &len);
+               words[i] = strdup(w);
+               if (!words[i]) {
+                       script_command_args_free(words, i);
+                       return NULL;
+               }
+       }
+       *nwords = i;
+       return words;
+}
+
+struct command_context *current_command_context(Jim_Interp *interp)
 {
-       assert(head);
-       for (struct command *cc = *head; cc; cc = cc->next)
-       {
-               if (strcmp(cc->name, name) == 0)
-                       return cc;
+       /* grab the command context from the associated data */
+       struct command_context *cmd_ctx = Jim_GetAssocData(interp, "context");
+       if (!cmd_ctx) {
+               /* Tcl can invoke commands directly instead of via command_run_line(). This would
+                * happen when the Jim Tcl interpreter is provided by eCos or if we are running
+                * commands in a startup script.
+                *
+                * A telnet or gdb server would provide a non-default command context to
+                * handle piping of error output, have a separate current target, etc.
+                */
+               cmd_ctx = global_cmd_ctx;
        }
-       return NULL;
+       return cmd_ctx;
 }
 
 /**
- * Add the command to the end of linked list.
- * @returns Returns false if the named command already exists in the list.
- * Returns true otherwise.
+ * Find a openocd command from fullname.
+ * @returns Returns the named command if it is registred in interp.
+ * Returns NULL otherwise.
  */
-static void command_add_child(struct command **head, struct command *c)
+static struct command *command_find_from_name(Jim_Interp *interp, const char *name)
 {
-       assert(head);
-       if (NULL == *head)
-       {
-               *head = c;
-               return;
-       }
-       struct command *cc = *head;
-       while (cc->next) cc = cc->next;
-       cc->next = c;
+       if (!name)
+               return NULL;
+
+       Jim_Obj *jim_name = Jim_NewStringObj(interp, name, -1);
+       Jim_IncrRefCount(jim_name);
+       Jim_Cmd *cmd = Jim_GetCommand(interp, jim_name, JIM_NONE);
+       Jim_DecrRefCount(interp, jim_name);
+       if (!cmd || jimcmd_is_proc(cmd) || !jimcmd_is_oocd_command(cmd))
+               return NULL;
+
+       return jimcmd_privdata(cmd);
 }
 
-struct command* register_command(struct command_context *context,
-               struct command *parent, char *name, command_handler_t handler,
-               enum command_mode mode, char *help)
+static struct command *command_new(struct command_context *cmd_ctx,
+       const char *full_name, const struct command_registration *cr)
 {
-       if (!context || !name)
+       assert(cr->name);
+
+       /*
+        * If it is a non-jim command with no .usage specified,
+        * log an error.
+        *
+        * strlen(.usage) == 0 means that the command takes no
+        * arguments.
+       */
+       if (!cr->jim_handler && !cr->usage)
+               LOG_ERROR("BUG: command '%s' does not have the "
+                       "'.usage' field filled out",
+                       full_name);
+
+       struct command *c = calloc(1, sizeof(struct command));
+       if (!c)
                return NULL;
 
-       struct command **head = parent ? &parent->children : &context->commands;
-       struct command *c = command_find(head, name);
-       if (NULL != c)
-               return c;
+       c->name = strdup(cr->name);
+       if (!c->name) {
+               free(c);
+               return NULL;
+       }
 
-       c = malloc(sizeof(struct command));
+       c->handler = cr->handler;
+       c->jim_handler = cr->jim_handler;
+       c->mode = cr->mode;
 
-       c->name = strdup(name);
-       c->parent = parent;
-       c->children = NULL;
-       c->handler = handler;
-       c->mode = mode;
-       c->next = NULL;
+       if (cr->help || cr->usage)
+               help_add_command(cmd_ctx, full_name, cr->help, cr->usage);
 
-       command_add_child(head, c);
+       return c;
+}
 
-       command_helptext_add(command_name_list(c), help);
+static void command_free(struct Jim_Interp *interp, void *priv)
+{
+       struct command *c = priv;
 
-       /* just a placeholder, no handler */
-       if (c->handler == NULL)
-               return c;
+       free(c->name);
+       free(c);
+}
 
-       const char *full_name = command_name(c, '_');
+static struct command *register_command(struct command_context *context,
+       const char *cmd_prefix, const struct command_registration *cr)
+{
+       char *full_name;
 
-       const char *ocd_name = alloc_printf("ocd_%s", full_name);
-       Jim_CreateCommand(interp, ocd_name, script_command, c, NULL);
-       free((void *)ocd_name);
+       if (!context || !cr->name)
+               return NULL;
 
-       /* we now need to add an overrideable proc */
-       const char *override_name = alloc_printf("proc %s {args} {"
-                       "if {[catch {eval ocd_%s $args}] == 0} "
-                       "{return \"\"} else {return -code error}}",
-                       full_name, full_name);
-       Jim_Eval_Named(interp, override_name, __THIS__FILE__, __LINE__);
-       free((void *)override_name);
+       if (cmd_prefix)
+               full_name = alloc_printf("%s %s", cmd_prefix, cr->name);
+       else
+               full_name = strdup(cr->name);
+       if (!full_name)
+               return NULL;
 
-       free((void *)full_name);
+       struct command *c = command_find_from_name(context->interp, full_name);
+       if (c) {
+               /* TODO: originally we treated attempting to register a cmd twice as an error
+                * Sometimes we need this behaviour, such as with flash banks.
+                * http://www.mail-archive.com/openocd-development@lists.berlios.de/msg11152.html */
+               LOG_DEBUG("command '%s' is already registered", full_name);
+               free(full_name);
+               return c;
+       }
 
+       c = command_new(context, full_name, cr);
+       if (!c) {
+               free(full_name);
+               return NULL;
+       }
+
+       if (false) /* too noisy with debug_level 3 */
+               LOG_DEBUG("registering '%s'...", full_name);
+       int retval = Jim_CreateCommand(context->interp, full_name,
+                               jim_command_dispatch, c, command_free);
+       if (retval != JIM_OK) {
+               command_run_linef(context, "del_help_text {%s}", full_name);
+               command_run_linef(context, "del_usage_text {%s}", full_name);
+               free(c);
+               free(full_name);
+               return NULL;
+       }
+
+       free(full_name);
        return c;
 }
 
-int unregister_all_commands(struct command_context *context)
+int __register_commands(struct command_context *cmd_ctx, const char *cmd_prefix,
+       const struct command_registration *cmds, void *data,
+       struct target *override_target)
+{
+       int retval = ERROR_OK;
+       unsigned i;
+       for (i = 0; cmds[i].name || cmds[i].chain; i++) {
+               const struct command_registration *cr = cmds + i;
+
+               struct command *c = NULL;
+               if (cr->name) {
+                       c = register_command(cmd_ctx, cmd_prefix, cr);
+                       if (!c) {
+                               retval = ERROR_FAIL;
+                               break;
+                       }
+                       c->jim_handler_data = data;
+                       c->jim_override_target = override_target;
+               }
+               if (cr->chain) {
+                       if (cr->name) {
+                               if (cmd_prefix) {
+                                       char *new_prefix = alloc_printf("%s %s", cmd_prefix, cr->name);
+                                       if (!new_prefix) {
+                                               retval = ERROR_FAIL;
+                                               break;
+                                       }
+                                       retval = __register_commands(cmd_ctx, new_prefix, cr->chain, data, override_target);
+                                       free(new_prefix);
+                               } else {
+                                       retval = __register_commands(cmd_ctx, cr->name, cr->chain, data, override_target);
+                               }
+                       } else {
+                               retval = __register_commands(cmd_ctx, cmd_prefix, cr->chain, data, override_target);
+                       }
+                       if (retval != ERROR_OK)
+                               break;
+               }
+       }
+       if (retval != ERROR_OK) {
+               for (unsigned j = 0; j < i; j++)
+                       unregister_command(cmd_ctx, cmd_prefix, cmds[j].name);
+       }
+       return retval;
+}
+
+static __attribute__ ((format (PRINTF_ATTRIBUTE_FORMAT, 2, 3)))
+int unregister_commands_match(struct command_context *cmd_ctx, const char *format, ...)
 {
-       struct command *c, *c2;
+       Jim_Interp *interp = cmd_ctx->interp;
+       va_list ap;
 
-       if (context == NULL)
-               return ERROR_OK;
+       va_start(ap, format);
+       char *query = alloc_vprintf(format, ap);
+       va_end(ap);
+       if (!query)
+               return ERROR_FAIL;
 
-       while (NULL != context->commands)
-       {
-               c = context->commands;
-
-               while (NULL != c->children)
-               {
-                       c2 = c->children;
-                       c->children = c->children->next;
-                       free(c2->name);
-                       c2->name = NULL;
-                       free(c2);
-                       c2 = NULL;
+       char *query_cmd = alloc_printf("info commands {%s}", query);
+       free(query);
+       if (!query_cmd)
+               return ERROR_FAIL;
+
+       int retval = Jim_EvalSource(interp, __THIS__FILE__, __LINE__, query_cmd);
+       free(query_cmd);
+       if (retval != JIM_OK)
+               return ERROR_FAIL;
+
+       Jim_Obj *list = Jim_GetResult(interp);
+       Jim_IncrRefCount(list);
+
+       int len = Jim_ListLength(interp, list);
+       for (int i = 0; i < len; i++) {
+               Jim_Obj *elem = Jim_ListGetIndex(interp, list, i);
+               Jim_IncrRefCount(elem);
+
+               const char *name = Jim_GetString(elem, NULL);
+               struct command *c = command_find_from_name(interp, name);
+               if (!c) {
+                       /* not openocd command */
+                       Jim_DecrRefCount(interp, elem);
+                       continue;
                }
+               if (false) /* too noisy with debug_level 3 */
+                       LOG_DEBUG("delete command \"%s\"", name);
+#if JIM_VERSION >= 80
+               Jim_DeleteCommand(interp, elem);
+#else
+               Jim_DeleteCommand(interp, name);
+#endif
 
-               context->commands = context->commands->next;
+               help_del_command(cmd_ctx, name);
 
-               free(c->name);
-               c->name = NULL;
-               free(c);
-               c = NULL;
+               Jim_DecrRefCount(interp, elem);
        }
 
+       Jim_DecrRefCount(interp, list);
        return ERROR_OK;
 }
 
-int unregister_command(struct command_context *context, char *name)
+int unregister_all_commands(struct command_context *context,
+       const char *cmd_prefix)
 {
-       struct command *c, *p = NULL, *c2;
-
-       if ((!context) || (!name))
-               return ERROR_INVALID_ARGUMENTS;
+       if (!context)
+               return ERROR_OK;
 
-       /* find command */
-       c = context->commands;
+       if (!cmd_prefix || !*cmd_prefix)
+               return unregister_commands_match(context, "*");
 
-       while (NULL != c)
-       {
-               if (strcmp(name, c->name) == 0)
-               {
-                       /* unlink command */
-                       if (p)
-                       {
-                               p->next = c->next;
-                       }
-                       else
-                       {
-                               /* first element in command list */
-                               context->commands = c->next;
-                       }
+       int retval = unregister_commands_match(context, "%s *", cmd_prefix);
+       if (retval != ERROR_OK)
+               return retval;
 
-                       /* unregister children */
-                       while (NULL != c->children)
-                       {
-                               c2 = c->children;
-                               c->children = c->children->next;
-                               free(c2->name);
-                               c2->name = NULL;
-                               free(c2);
-                               c2 = NULL;
-                       }
+       return unregister_commands_match(context, "%s", cmd_prefix);
+}
 
-                       /* delete command */
-                       free(c->name);
-                       c->name = NULL;
-                       free(c);
-                       c = NULL;
-                       return ERROR_OK;
-               }
+static int unregister_command(struct command_context *context,
+       const char *cmd_prefix, const char *name)
+{
+       if (!context || !name)
+               return ERROR_COMMAND_SYNTAX_ERROR;
 
-               /* remember the last command for unlinking */
-               p = c;
-               c = c->next;
-       }
+       if (!cmd_prefix || !*cmd_prefix)
+               return unregister_commands_match(context, "%s", name);
 
-       return ERROR_OK;
+       return unregister_commands_match(context, "%s %s", cmd_prefix, name);
 }
 
 void command_output_text(struct command_context *context, const char *data)
 {
-       if (context && context->output_handler && data) {
+       if (context && context->output_handler && data)
                context->output_handler(context, data);
-       }
 }
 
-void command_print_sameline(struct command_context *context, const char *format, ...)
+void command_print_sameline(struct command_invocation *cmd, const char *format, ...)
 {
        char *string;
 
@@ -373,23 +455,22 @@ void command_print_sameline(struct command_context *context, const char *format,
        va_start(ap, format);
 
        string = alloc_vprintf(format, ap);
-       if (string != NULL)
-       {
+       if (string && cmd) {
                /* we want this collected in the log + we also want to pick it up as a tcl return
                 * value.
                 *
                 * The latter bit isn't precisely neat, but will do for now.
                 */
-               LOG_USER_N("%s", string);
-               /* We already printed it above */
-               /* command_output_text(context, string); */
+               Jim_AppendString(cmd->ctx->interp, cmd->output, string, -1);
+               /* We already printed it above
+                * command_output_text(context, string); */
                free(string);
        }
 
        va_end(ap);
 }
 
-void command_print(struct command_context *context, const char *format, ...)
+void command_print(struct command_invocation *cmd, const char *format, ...)
 {
        char *string;
 
@@ -397,80 +478,75 @@ void command_print(struct command_context *context, const char *format, ...)
        va_start(ap, format);
 
        string = alloc_vprintf(format, ap);
-       if (string != NULL)
-       {
-               strcat(string, "\n"); /* alloc_vprintf guaranteed the buffer to be at least one char longer */
+       if (string && cmd) {
+               strcat(string, "\n");   /* alloc_vprintf guaranteed the buffer to be at least one
+                                        *char longer */
                /* we want this collected in the log + we also want to pick it up as a tcl return
                 * value.
                 *
                 * The latter bit isn't precisely neat, but will do for now.
                 */
-               LOG_USER_N("%s", string);
-               /* We already printed it above */
-               /* command_output_text(context, string); */
+               Jim_AppendString(cmd->ctx->interp, cmd->output, string, -1);
+               /* We already printed it above
+                * command_output_text(context, string); */
                free(string);
        }
 
        va_end(ap);
 }
 
-static char *__command_name(struct command *c, char delim, unsigned extra)
+static bool command_can_run(struct command_context *cmd_ctx, struct command *c, const char *full_name)
 {
-       char *name;
-       unsigned len = strlen(c->name);
-       if (NULL == c->parent) {
-               // allocate enough for the name, child names, and '\0'
-               name = malloc(len + extra + 1);
-               strcpy(name, c->name);
-       } else {
-               // parent's extra must include both the space and name
-               name = __command_name(c->parent, delim, 1 + len + extra);
-               char dstr[2] = { delim, 0 };
-               strcat(name, dstr);
-               strcat(name, c->name);
+       if (c->mode == COMMAND_ANY || c->mode == cmd_ctx->mode)
+               return true;
+
+       /* Many commands may be run only before/after 'init' */
+       const char *when;
+       switch (c->mode) {
+               case COMMAND_CONFIG:
+                       when = "before";
+                       break;
+               case COMMAND_EXEC:
+                       when = "after";
+                       break;
+               /* handle the impossible with humor; it guarantees a bug report! */
+               default:
+                       when = "if Cthulhu is summoned by";
+                       break;
        }
-       return name;
-}
-char *command_name(struct command *c, char delim)
-{
-       return __command_name(c, delim, 0);
+       LOG_ERROR("The '%s' command must be used %s 'init'.",
+                       full_name ? full_name : c->name, when);
+       return false;
 }
 
 static int run_command(struct command_context *context,
-               struct command *c, const char *words[], unsigned num_words)
+       struct command *c, const char **words, unsigned num_words)
 {
-       int start_word = 0;
-       if (!((context->mode == COMMAND_CONFIG) || (c->mode == COMMAND_ANY) || (c->mode == context->mode)))
-       {
-               /* Config commands can not run after the config stage */
-               LOG_ERROR("Command '%s' only runs during configuration stage", c->name);
-               return ERROR_FAIL;
-       }
-
-       unsigned argc = num_words - start_word - 1;
-       const char **args = words + start_word + 1;
-       int retval = c->handler(context, args, argc);
-       if (retval == ERROR_COMMAND_SYNTAX_ERROR)
-       {
+       struct command_invocation cmd = {
+               .ctx = context,
+               .current = c,
+               .name = c->name,
+               .argc = num_words - 1,
+               .argv = words + 1,
+       };
+
+       cmd.output = Jim_NewEmptyStringObj(context->interp);
+       Jim_IncrRefCount(cmd.output);
+
+       int retval = c->handler(&cmd);
+       if (retval == ERROR_COMMAND_SYNTAX_ERROR) {
                /* Print help for command */
-               char *full_name = command_name(c, ' ');
-               if (NULL != full_name) {
-                       command_run_linef(context, "help %s", full_name);
-                       free(full_name);
-               } else
-                       retval = -ENOMEM;
-       }
-       else if (retval == ERROR_COMMAND_CLOSE_CONNECTION)
-       {
+               command_run_linef(context, "usage %s", words[0]);
+       } else if (retval == ERROR_COMMAND_CLOSE_CONNECTION) {
                /* just fall through for a shutdown request */
+       } else {
+               if (retval != ERROR_OK)
+                       LOG_DEBUG("Command '%s' failed with error code %d",
+                                               words[0], retval);
+               /* Use the command output as the Tcl result */
+               Jim_SetResult(context->interp, cmd.output);
        }
-       else if (retval != ERROR_OK)
-       {
-               /* we do not print out an error message because the command *should*
-                * have printed out an error
-                */
-               LOG_DEBUG("Command failed with error code %d", retval);
-       }
+       Jim_DecrRefCount(context->interp, cmd.output);
 
        return retval;
 }
@@ -489,59 +565,55 @@ int command_run_line(struct command_context *context, char *line)
         * happen when the Jim Tcl interpreter is provided by eCos for
         * instance.
         */
+       struct target *saved_target_override = context->current_target_override;
+       context->current_target_override = NULL;
+
+       Jim_Interp *interp = context->interp;
+       struct command_context *old_context = Jim_GetAssocData(interp, "context");
        Jim_DeleteAssocData(interp, "context");
        retcode = Jim_SetAssocData(interp, "context", NULL, context);
-       if (retcode == JIM_OK)
-       {
+       if (retcode == JIM_OK) {
                /* associated the return value */
                Jim_DeleteAssocData(interp, "retval");
                retcode = Jim_SetAssocData(interp, "retval", NULL, &retval);
-               if (retcode == JIM_OK)
-               {
-                       retcode = Jim_Eval_Named(interp, line, __THIS__FILE__, __LINE__);
+               if (retcode == JIM_OK) {
+                       retcode = Jim_Eval_Named(interp, line, 0, 0);
 
                        Jim_DeleteAssocData(interp, "retval");
                }
                Jim_DeleteAssocData(interp, "context");
+               int inner_retcode = Jim_SetAssocData(interp, "context", NULL, old_context);
+               if (retcode == JIM_OK)
+                       retcode = inner_retcode;
        }
-       if (retcode == JIM_ERR) {
-               if (retval != ERROR_COMMAND_CLOSE_CONNECTION)
-               {
-                       /* We do not print the connection closed error message */
-                       Jim_PrintErrorMessage(interp);
-               }
-               if (retval == ERROR_OK)
-               {
-                       /* It wasn't a low level OpenOCD command that failed */
-                       return ERROR_FAIL;
-               }
-               return retval;
-       } else if (retcode == JIM_EXIT) {
-               /* ignore. */
-               /* exit(Jim_GetExitCode(interp)); */
-       } else {
+       context->current_target_override = saved_target_override;
+       if (retcode == JIM_OK) {
                const char *result;
                int reslen;
 
                result = Jim_GetString(Jim_GetResult(interp), &reslen);
-               if (reslen > 0)
-               {
-                       int i;
-                       char buff[256 + 1];
-                       for (i = 0; i < reslen; i += 256)
-                       {
-                               int chunk;
-                               chunk = reslen - i;
-                               if (chunk > 256)
-                                       chunk = 256;
-                               strncpy(buff, result + i, chunk);
-                               buff[chunk] = 0;
-                               LOG_USER_N("%s", buff);
-                       }
-                       LOG_USER_N("%s", "\n");
+               if (reslen > 0) {
+                       command_output_text(context, result);
+                       command_output_text(context, "\n");
                }
                retval = ERROR_OK;
+       } else if (retcode == JIM_EXIT) {
+               /* ignore.
+                * exit(Jim_GetExitCode(interp)); */
+       } else if (retcode == ERROR_COMMAND_CLOSE_CONNECTION) {
+               return retcode;
+       } else {
+               Jim_MakeErrorMessage(interp);
+               /* error is broadcast */
+               LOG_USER("%s", Jim_GetString(Jim_GetResult(interp), NULL));
+
+               if (retval == ERROR_OK) {
+                       /* It wasn't a low level OpenOCD command that failed */
+                       return ERROR_FAIL;
+               }
+               return retval;
        }
+
        return retval;
 }
 
@@ -552,36 +624,36 @@ int command_run_linef(struct command_context *context, const char *format, ...)
        va_list ap;
        va_start(ap, format);
        string = alloc_vprintf(format, ap);
-       if (string != NULL)
-       {
+       if (string) {
                retval = command_run_line(context, string);
+               free(string);
        }
        va_end(ap);
        return retval;
 }
 
-void command_set_output_handler(struct command_contextcontext,
-               command_output_handler_t output_handler, void *priv)
+void command_set_output_handler(struct command_context *context,
+       command_output_handler_t output_handler, void *priv)
 {
        context->output_handler = output_handler;
        context->output_handler_priv = priv;
 }
 
-struct command_context* copy_command_context(struct command_context* context)
+struct command_context *copy_command_context(struct command_context *context)
 {
-       struct command_contextcopy_context = malloc(sizeof(struct command_context));
+       struct command_context *copy_context = malloc(sizeof(struct command_context));
 
        *copy_context = *context;
 
        return copy_context;
 }
 
-int command_done(struct command_context *context)
+void command_done(struct command_context *cmd_ctx)
 {
-       free(context);
-       context = NULL;
+       if (!cmd_ctx)
+               return;
 
-       return ERROR_OK;
+       free(cmd_ctx);
 }
 
 /* find full path to file */
@@ -591,7 +663,7 @@ static int jim_find(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
                return JIM_ERR;
        const char *file = Jim_GetString(argv[1], NULL);
        char *full_path = find_file(file);
-       if (full_path == NULL)
+       if (!full_path)
                return JIM_ERR;
        Jim_Obj *result = Jim_NewStringObj(interp, full_path, strlen(full_path));
        free(full_path);
@@ -600,356 +672,746 @@ static int jim_find(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
        return JIM_OK;
 }
 
-static int jim_echo(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+COMMAND_HANDLER(handle_echo)
+{
+       if (CMD_ARGC == 2 && !strcmp(CMD_ARGV[0], "-n")) {
+               LOG_USER_N("%s", CMD_ARGV[1]);
+               return ERROR_OK;
+       }
+
+       if (CMD_ARGC != 1)
+               return ERROR_FAIL;
+
+       LOG_USER("%s", CMD_ARGV[0]);
+       return ERROR_OK;
+}
+
+/* Capture progress output and return as tcl return value. If the
+ * progress output was empty, return tcl return value.
+ */
+static int jim_capture(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 {
        if (argc != 2)
                return JIM_ERR;
+
+       struct log_capture_state *state = command_log_capture_start(interp);
+
+       /* disable polling during capture. This avoids capturing output
+        * from polling.
+        *
+        * This is necessary in order to avoid accidentally getting a non-empty
+        * string for tcl fn's.
+        */
+       bool save_poll_mask = jtag_poll_mask();
+
        const char *str = Jim_GetString(argv[1], NULL);
-       LOG_USER("%s", str);
-       return JIM_OK;
+       int retcode = Jim_Eval_Named(interp, str, __THIS__FILE__, __LINE__);
+
+       jtag_poll_unmask(save_poll_mask);
+
+       command_log_capture_finish(state);
+
+       return retcode;
 }
 
-static size_t openocd_jim_fwrite(const void *_ptr, size_t size, size_t n, void *cookie)
+struct help_entry {
+       struct list_head lh;
+       char *cmd_name;
+       char *help;
+       char *usage;
+};
+
+static COMMAND_HELPER(command_help_show, struct help_entry *c,
+       bool show_help, const char *cmd_match);
+
+static COMMAND_HELPER(command_help_show_list, bool show_help, const char *cmd_match)
 {
-       size_t nbytes;
-       const char *ptr;
-       Jim_Interp *interp;
+       struct help_entry *entry;
+
+       list_for_each_entry(entry, CMD_CTX->help_list, lh)
+               CALL_COMMAND_HANDLER(command_help_show, entry, show_help, cmd_match);
+       return ERROR_OK;
+}
 
-       /* make it a char easier to read code */
-       ptr = _ptr;
-       interp = cookie;
-       nbytes = size * n;
-       if (ptr == NULL || interp == NULL || nbytes == 0) {
-               return 0;
+#define HELP_LINE_WIDTH(_n) (int)(76 - (2 * _n))
+
+static void command_help_show_indent(unsigned n)
+{
+       for (unsigned i = 0; i < n; i++)
+               LOG_USER_N("  ");
+}
+static void command_help_show_wrap(const char *str, unsigned n, unsigned n2)
+{
+       const char *cp = str, *last = str;
+       while (*cp) {
+               const char *next = last;
+               do {
+                       cp = next;
+                       do {
+                               next++;
+                       } while (*next != ' ' && *next != '\t' && *next != '\0');
+               } while ((next - last < HELP_LINE_WIDTH(n)) && *next != '\0');
+               if (next - last < HELP_LINE_WIDTH(n))
+                       cp = next;
+               command_help_show_indent(n);
+               LOG_USER("%.*s", (int)(cp - last), last);
+               last = cp + 1;
+               n = n2;
        }
+}
 
-       /* do we have to chunk it? */
-       if (ptr[nbytes] == 0)
-       {
-               /* no it is a C style string */
-               LOG_USER_N("%s", ptr);
-               return strlen(ptr);
-       }
-       /* GRR we must chunk - not null terminated */
-       while (nbytes) {
-               char chunk[128 + 1];
-               int x;
-
-               x = nbytes;
-               if (x > 128) {
-                       x = 128;
+static COMMAND_HELPER(command_help_show, struct help_entry *c,
+       bool show_help, const char *cmd_match)
+{
+       unsigned int n = 0;
+       for (const char *s = strchr(c->cmd_name, ' '); s; s = strchr(s + 1, ' '))
+               n++;
+
+       /* If the match string occurs anywhere, we print out
+        * stuff for this command. */
+       bool is_match = strstr(c->cmd_name, cmd_match) ||
+               (c->usage && strstr(c->usage, cmd_match)) ||
+               (c->help && strstr(c->help, cmd_match));
+
+       if (is_match) {
+               if (c->usage && strlen(c->usage) > 0) {
+                       char *msg = alloc_printf("%s %s", c->cmd_name, c->usage);
+                       command_help_show_wrap(msg, n, n + 5);
+                       free(msg);
+               } else {
+                       command_help_show_wrap(c->cmd_name, n, n + 5);
                }
-               /* copy it */
-               memcpy(chunk, ptr, x);
-               /* terminate it */
-               chunk[n] = 0;
-               /* output it */
-               LOG_USER_N("%s", chunk);
-               ptr += x;
-               nbytes -= x;
        }
 
-       return n;
+       if (is_match && show_help) {
+               char *msg;
+
+               /* TODO: factorize jim_command_mode() to avoid running jim command here */
+               char *request = alloc_printf("command mode %s", c->cmd_name);
+               if (!request) {
+                       LOG_ERROR("Out of memory");
+                       return ERROR_FAIL;
+               }
+               int retval = Jim_Eval(CMD_CTX->interp, request);
+               free(request);
+               enum command_mode mode = COMMAND_UNKNOWN;
+               if (retval != JIM_ERR) {
+                       const char *result = Jim_GetString(Jim_GetResult(CMD_CTX->interp), NULL);
+                       if (!strcmp(result, "any"))
+                               mode = COMMAND_ANY;
+                       else if (!strcmp(result, "config"))
+                               mode = COMMAND_CONFIG;
+                       else if (!strcmp(result, "exec"))
+                               mode = COMMAND_EXEC;
+               }
+
+               /* Normal commands are runtime-only; highlight exceptions */
+               if (mode != COMMAND_EXEC) {
+                       const char *stage_msg = "";
+
+                       switch (mode) {
+                               case COMMAND_CONFIG:
+                                       stage_msg = " (configuration command)";
+                                       break;
+                               case COMMAND_ANY:
+                                       stage_msg = " (command valid any time)";
+                                       break;
+                               default:
+                                       stage_msg = " (?mode error?)";
+                                       break;
+                       }
+                       msg = alloc_printf("%s%s", c->help ? c->help : "", stage_msg);
+               } else
+                       msg = alloc_printf("%s", c->help ? c->help : "");
+
+               if (msg) {
+                       command_help_show_wrap(msg, n + 3, n + 3);
+                       free(msg);
+               } else
+                       return -ENOMEM;
+       }
+
+       return ERROR_OK;
 }
 
-static size_t openocd_jim_fread(void *ptr, size_t size, size_t n, void *cookie)
+COMMAND_HANDLER(handle_help_command)
 {
-       /* TCL wants to read... tell him no */
-       return 0;
+       bool full = strcmp(CMD_NAME, "help") == 0;
+       int retval;
+       char *cmd_match;
+
+       if (CMD_ARGC <= 0)
+               cmd_match = strdup("");
+
+       else {
+               cmd_match = strdup(CMD_ARGV[0]);
+
+               for (unsigned int i = 1; i < CMD_ARGC && cmd_match; ++i) {
+                       char *prev = cmd_match;
+                       cmd_match = alloc_printf("%s %s", prev, CMD_ARGV[i]);
+                       free(prev);
+               }
+       }
+
+       if (!cmd_match) {
+               LOG_ERROR("unable to build search string");
+               return -ENOMEM;
+       }
+       retval = CALL_COMMAND_HANDLER(command_help_show_list, full, cmd_match);
+
+       free(cmd_match);
+       return retval;
 }
 
-static int openocd_jim_vfprintf(void *cookie, const char *fmt, va_list ap)
+static char *alloc_concatenate_strings(int argc, Jim_Obj * const *argv)
 {
-       char *cp;
-       int n;
-       Jim_Interp *interp;
+       char *prev, *all;
+       int i;
 
-       n = -1;
-       interp = cookie;
-       if (interp == NULL)
-               return n;
+       assert(argc >= 1);
 
-       cp = alloc_vprintf(fmt, ap);
-       if (cp)
-       {
-               LOG_USER_N("%s", cp);
-               n = strlen(cp);
-               free(cp);
+       all = strdup(Jim_GetString(argv[0], NULL));
+       if (!all) {
+               LOG_ERROR("Out of memory");
+               return NULL;
+       }
+
+       for (i = 1; i < argc; ++i) {
+               prev = all;
+               all = alloc_printf("%s %s", all, Jim_GetString(argv[i], NULL));
+               free(prev);
+               if (!all) {
+                       LOG_ERROR("Out of memory");
+                       return NULL;
+               }
        }
-       return n;
+
+       return all;
 }
 
-static int openocd_jim_fflush(void *cookie)
+static int exec_command(Jim_Interp *interp, struct command_context *cmd_ctx,
+               struct command *c, int argc, Jim_Obj * const *argv)
 {
-       /* nothing to flush */
-       return 0;
+       if (c->jim_handler)
+               return c->jim_handler(interp, argc, argv);
+
+       /* use c->handler */
+       unsigned int nwords;
+       char **words = script_command_args_alloc(argc, argv, &nwords);
+       if (!words)
+               return JIM_ERR;
+
+       int retval = run_command(cmd_ctx, c, (const char **)words, nwords);
+       script_command_args_free(words, nwords);
+       return command_retval_set(interp, retval);
 }
 
-static char* openocd_jim_fgets(char *s, int size, void *cookie)
+static int jim_command_dispatch(Jim_Interp *interp, int argc, Jim_Obj * const *argv)
 {
-       /* not supported */
-       errno = ENOTSUP;
-       return NULL;
+       /* check subcommands */
+       if (argc > 1) {
+               char *s = alloc_printf("%s %s", Jim_GetString(argv[0], NULL), Jim_GetString(argv[1], NULL));
+               Jim_Obj *js = Jim_NewStringObj(interp, s, -1);
+               Jim_IncrRefCount(js);
+               free(s);
+               Jim_Cmd *cmd = Jim_GetCommand(interp, js, JIM_NONE);
+               if (cmd) {
+                       int retval = Jim_EvalObjPrefix(interp, js, argc - 2, argv + 2);
+                       Jim_DecrRefCount(interp, js);
+                       return retval;
+               }
+               Jim_DecrRefCount(interp, js);
+       }
+
+       script_debug(interp, argc, argv);
+
+       struct command *c = jim_to_command(interp);
+       if (!c->jim_handler && !c->handler) {
+               Jim_EvalObjPrefix(interp, Jim_NewStringObj(interp, "usage", -1), 1, argv);
+               return JIM_ERR;
+       }
+
+       struct command_context *cmd_ctx = current_command_context(interp);
+
+       if (!command_can_run(cmd_ctx, c, Jim_GetString(argv[0], NULL)))
+               return JIM_ERR;
+
+       /*
+        * TODO: to be removed after v0.12.0
+        * workaround for https://sourceforge.net/p/openocd/tickets/362/
+        * After syntax change of "expr" in jimtcl 0.81
+        * the replacement of jimtcl "expr" with openocd version in
+        * https://review.openocd.org/6510/
+        * introduces too many target polling during math expressions with
+        * "expr" commands.
+        * After v0.12.0 replace the following two lines with
+        * target_call_timer_callbacks();
+        */
+       if (strcmp(c->name, "expr"))
+               target_call_timer_callbacks_now();
+
+       /*
+        * Black magic of overridden current target:
+        * If the command we are going to handle has a target prefix,
+        * override the current target temporarily for the time
+        * of processing the command.
+        * current_target_override is used also for event handlers
+        * therefore we prevent touching it if command has no prefix.
+        * Previous override is saved and restored back to ensure
+        * correct work when jim_command_dispatch() is re-entered.
+        */
+       struct target *saved_target_override = cmd_ctx->current_target_override;
+       if (c->jim_override_target)
+               cmd_ctx->current_target_override = c->jim_override_target;
+
+       int retval = exec_command(interp, cmd_ctx, c, argc, argv);
+
+       if (c->jim_override_target)
+               cmd_ctx->current_target_override = saved_target_override;
+
+       return retval;
 }
 
-static int jim_capture(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+static int jim_command_mode(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 {
-       if (argc != 2)
-               return JIM_ERR;
-       int retcode;
-       const char *str = Jim_GetString(argv[1], NULL);
+       struct command_context *cmd_ctx = current_command_context(interp);
+       enum command_mode mode;
 
-       /* capture log output and return it */
-       Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
-       /* a garbage collect can happen, so we need a reference count to this object */
-       Jim_IncrRefCount(tclOutput);
+       if (argc > 1) {
+               char *full_name = alloc_concatenate_strings(argc - 1, argv + 1);
+               if (!full_name)
+                       return JIM_ERR;
+               Jim_Obj *s = Jim_NewStringObj(interp, full_name, -1);
+               Jim_IncrRefCount(s);
+               Jim_Cmd *cmd = Jim_GetCommand(interp, s, JIM_NONE);
+               Jim_DecrRefCount(interp, s);
+               free(full_name);
+               if (!cmd || !(jimcmd_is_proc(cmd) || jimcmd_is_oocd_command(cmd))) {
+                       Jim_SetResultString(interp, "unknown", -1);
+                       return JIM_OK;
+               }
+
+               if (jimcmd_is_proc(cmd)) {
+                       /* tcl proc */
+                       mode = COMMAND_ANY;
+               } else {
+                       struct command *c = jimcmd_privdata(cmd);
 
-       log_add_callback(tcl_output, tclOutput);
+                       mode = c->mode;
+               }
+       } else
+               mode = cmd_ctx->mode;
 
-       retcode = Jim_Eval_Named(interp, str, __THIS__FILE__, __LINE__);
+       const char *mode_str;
+       switch (mode) {
+               case COMMAND_ANY:
+                       mode_str = "any";
+                       break;
+               case COMMAND_CONFIG:
+                       mode_str = "config";
+                       break;
+               case COMMAND_EXEC:
+                       mode_str = "exec";
+                       break;
+               default:
+                       mode_str = "unknown";
+                       break;
+       }
+       Jim_SetResultString(interp, mode_str, -1);
+       return JIM_OK;
+}
 
-       log_remove_callback(tcl_output, tclOutput);
+int help_del_all_commands(struct command_context *cmd_ctx)
+{
+       struct help_entry *curr, *n;
+
+       list_for_each_entry_safe(curr, n, cmd_ctx->help_list, lh) {
+               list_del(&curr->lh);
+               free(curr->cmd_name);
+               free(curr->help);
+               free(curr->usage);
+               free(curr);
+       }
+       return ERROR_OK;
+}
 
-       /* We dump output into this local variable */
-       Jim_SetResult(interp, tclOutput);
-       Jim_DecrRefCount(interp, tclOutput);
+static int help_del_command(struct command_context *cmd_ctx, const char *cmd_name)
+{
+       struct help_entry *curr;
+
+       list_for_each_entry(curr, cmd_ctx->help_list, lh) {
+               if (!strcmp(cmd_name, curr->cmd_name)) {
+                       list_del(&curr->lh);
+                       free(curr->cmd_name);
+                       free(curr->help);
+                       free(curr->usage);
+                       free(curr);
+                       break;
+               }
+       }
 
-       return retcode;
+       return ERROR_OK;
 }
 
-/* sleep command sleeps for <n> miliseconds
+static int help_add_command(struct command_context *cmd_ctx,
+       const char *cmd_name, const char *help_text, const char *usage_text)
+{
+       int cmp = -1; /* add after curr */
+       struct help_entry *curr;
+
+       list_for_each_entry_reverse(curr, cmd_ctx->help_list, lh) {
+               cmp = strcmp(cmd_name, curr->cmd_name);
+               if (cmp >= 0)
+                       break;
+       }
+
+       struct help_entry *entry;
+       if (cmp) {
+               entry = calloc(1, sizeof(*entry));
+               if (!entry) {
+                       LOG_ERROR("Out of memory");
+                       return ERROR_FAIL;
+               }
+               entry->cmd_name = strdup(cmd_name);
+               if (!entry->cmd_name) {
+                       LOG_ERROR("Out of memory");
+                       free(entry);
+                       return ERROR_FAIL;
+               }
+               list_add(&entry->lh, &curr->lh);
+       } else {
+               entry = curr;
+       }
+
+       if (help_text) {
+               char *text = strdup(help_text);
+               if (!text) {
+                       LOG_ERROR("Out of memory");
+                       return ERROR_FAIL;
+               }
+               free(entry->help);
+               entry->help = text;
+       }
+
+       if (usage_text) {
+               char *text = strdup(usage_text);
+               if (!text) {
+                       LOG_ERROR("Out of memory");
+                       return ERROR_FAIL;
+               }
+               free(entry->usage);
+               entry->usage = text;
+       }
+
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(handle_help_add_command)
+{
+       if (CMD_ARGC != 2)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       const char *help = !strcmp(CMD_NAME, "add_help_text") ? CMD_ARGV[1] : NULL;
+       const char *usage = !strcmp(CMD_NAME, "add_usage_text") ? CMD_ARGV[1] : NULL;
+       if (!help && !usage) {
+               LOG_ERROR("command name '%s' is unknown", CMD_NAME);
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+       const char *cmd_name = CMD_ARGV[0];
+       return help_add_command(CMD_CTX, cmd_name, help, usage);
+}
+
+/* sleep command sleeps for <n> milliseconds
  * this is useful in target startup scripts
  */
 COMMAND_HANDLER(handle_sleep_command)
 {
        bool busy = false;
-       if (CMD_ARGC == 2)
-       {
-               if (strcmp(args[1], "busy") == 0)
+       if (CMD_ARGC == 2) {
+               if (strcmp(CMD_ARGV[1], "busy") == 0)
                        busy = true;
                else
                        return ERROR_COMMAND_SYNTAX_ERROR;
-       }
-       else if (CMD_ARGC < 1 || CMD_ARGC > 2)
+       } else if (CMD_ARGC < 1 || CMD_ARGC > 2)
                return ERROR_COMMAND_SYNTAX_ERROR;
 
        unsigned long duration = 0;
-       int retval = parse_ulong(args[0], &duration);
-       if (ERROR_OK != retval)
+       int retval = parse_ulong(CMD_ARGV[0], &duration);
+       if (retval != ERROR_OK)
                return retval;
 
-       if (!busy)
-       {
-               long long then = timeval_ms();
-               while (timeval_ms() - then < (long long)duration)
-               {
+       if (!busy) {
+               int64_t then = timeval_ms();
+               while (timeval_ms() - then < (int64_t)duration) {
                        target_call_timer_callbacks_now();
+                       keep_alive();
                        usleep(1000);
                }
-       }
-       else
+       } else
                busy_sleep(duration);
 
        return ERROR_OK;
 }
 
-COMMAND_HANDLER(handle_fast_command)
+static const struct command_registration command_subcommand_handlers[] = {
+       {
+               .name = "mode",
+               .mode = COMMAND_ANY,
+               .jim_handler = jim_command_mode,
+               .usage = "[command_name ...]",
+               .help = "Returns the command modes allowed by a command: "
+                       "'any', 'config', or 'exec'. If no command is "
+                       "specified, returns the current command mode. "
+                       "Returns 'unknown' if an unknown command is given. "
+                       "Command can be multiple tokens.",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+static const struct command_registration command_builtin_handlers[] = {
+       {
+               .name = "ocd_find",
+               .mode = COMMAND_ANY,
+               .jim_handler = jim_find,
+               .help = "find full path to file",
+               .usage = "file",
+       },
+       {
+               .name = "capture",
+               .mode = COMMAND_ANY,
+               .jim_handler = jim_capture,
+               .help = "Capture progress output and return as tcl return value. If the "
+                               "progress output was empty, return tcl return value.",
+               .usage = "command",
+       },
+       {
+               .name = "echo",
+               .handler = handle_echo,
+               .mode = COMMAND_ANY,
+               .help = "Logs a message at \"user\" priority. "
+                       "Option \"-n\" suppresses trailing newline",
+               .usage = "[-n] string",
+       },
+       {
+               .name = "add_help_text",
+               .handler = handle_help_add_command,
+               .mode = COMMAND_ANY,
+               .help = "Add new command help text; "
+                       "Command can be multiple tokens.",
+               .usage = "command_name helptext_string",
+       },
+       {
+               .name = "add_usage_text",
+               .handler = handle_help_add_command,
+               .mode = COMMAND_ANY,
+               .help = "Add new command usage text; "
+                       "command can be multiple tokens.",
+               .usage = "command_name usage_string",
+       },
+       {
+               .name = "sleep",
+               .handler = handle_sleep_command,
+               .mode = COMMAND_ANY,
+               .help = "Sleep for specified number of milliseconds.  "
+                       "\"busy\" will busy wait instead (avoid this).",
+               .usage = "milliseconds ['busy']",
+       },
+       {
+               .name = "help",
+               .handler = handle_help_command,
+               .mode = COMMAND_ANY,
+               .help = "Show full command help; "
+                       "command can be multiple tokens.",
+               .usage = "[command_name]",
+       },
+       {
+               .name = "usage",
+               .handler = handle_help_command,
+               .mode = COMMAND_ANY,
+               .help = "Show basic command usage; "
+                       "command can be multiple tokens.",
+               .usage = "[command_name]",
+       },
+       {
+               .name = "command",
+               .mode = COMMAND_ANY,
+               .help = "core command group (introspection)",
+               .chain = command_subcommand_handlers,
+               .usage = "",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+struct command_context *command_init(const char *startup_tcl, Jim_Interp *interp)
 {
-       if (CMD_ARGC != 1)
-               return ERROR_COMMAND_SYNTAX_ERROR;
+       struct command_context *context = calloc(1, sizeof(struct command_context));
 
-       fast_and_dangerous = strcmp("enable", args[0]) == 0;
+       context->mode = COMMAND_EXEC;
 
-       return ERROR_OK;
-}
+       /* context can be duplicated. Put list head on separate mem-chunk to keep list consistent */
+       context->help_list = malloc(sizeof(*context->help_list));
+       INIT_LIST_HEAD(context->help_list);
+
+       /* Create a jim interpreter if we were not handed one */
+       if (!interp) {
+               /* Create an interpreter */
+               interp = Jim_CreateInterp();
+               /* Add all the Jim core commands */
+               Jim_RegisterCoreCommands(interp);
+               Jim_InitStaticExtensions(interp);
+       }
 
+       context->interp = interp;
 
-struct command_context* command_init()
-{
-       struct command_context* context = malloc(sizeof(struct command_context));
-       extern const char startup_tcl[];
-       const char *HostOs;
+       register_commands(context, NULL, command_builtin_handlers);
 
-       context->mode = COMMAND_EXEC;
-       context->commands = NULL;
-       context->current_target = 0;
-       context->output_handler = NULL;
-       context->output_handler_priv = NULL;
-
-#if !BUILD_ECOSBOARD
-       Jim_InitEmbedded();
-       /* Create an interpreter */
-       interp = Jim_CreateInterp();
-       /* Add all the Jim core commands */
-       Jim_RegisterCoreCommands(interp);
-#endif
-
-#if defined(_MSC_VER)
-       /* WinXX - is generic, the forward
-        * looking problem is this:
-        *
-        *   "win32" or "win64"
-        *
-        * "winxx" is generic.
-        */
-       HostOs = "winxx";
-#elif defined(__linux__)
-       HostOs = "linux";
-#elif defined(__DARWIN__)
-       HostOs = "darwin";
-#elif defined(__CYGWIN__)
-       HostOs = "cygwin";
-#elif defined(__MINGW32__)
-       HostOs = "mingw32";
-#elif defined(__ECOS)
-       HostOs = "ecos";
-#else
-#warn unrecognized host OS...
-       HostOs = "other";
-#endif
-       Jim_SetGlobalVariableStr(interp, "ocd_HOSTOS",
-                       Jim_NewStringObj(interp, HostOs , strlen(HostOs)));
-
-       Jim_CreateCommand(interp, "ocd_find", jim_find, NULL, NULL);
-       Jim_CreateCommand(interp, "echo", jim_echo, NULL, NULL);
-       Jim_CreateCommand(interp, "capture", jim_capture, NULL, NULL);
-
-       /* Set Jim's STDIO */
-       interp->cookie_stdin = interp;
-       interp->cookie_stdout = interp;
-       interp->cookie_stderr = interp;
-       interp->cb_fwrite = openocd_jim_fwrite;
-       interp->cb_fread = openocd_jim_fread ;
-       interp->cb_vfprintf = openocd_jim_vfprintf;
-       interp->cb_fflush = openocd_jim_fflush;
-       interp->cb_fgets = openocd_jim_fgets;
-
-#if !BUILD_ECOSBOARD
-       Jim_EventLoopOnLoad(interp);
-#endif
-       if (Jim_Eval_Named(interp, startup_tcl, "embedded:startup.tcl",1) == JIM_ERR)
-       {
+       Jim_SetAssocData(interp, "context", NULL, context);
+       if (Jim_Eval_Named(interp, startup_tcl, "embedded:startup.tcl", 1) == JIM_ERR) {
                LOG_ERROR("Failed to run startup.tcl (embedded into OpenOCD)");
-               Jim_PrintErrorMessage(interp);
+               Jim_MakeErrorMessage(interp);
+               LOG_USER_N("%s", Jim_GetString(Jim_GetResult(interp), NULL));
                exit(-1);
        }
-
-       register_command(context, NULL, "sleep",
-                       handle_sleep_command, COMMAND_ANY,
-                       "<n> [busy] - sleep for n milliseconds. "
-                       "\"busy\" means busy wait");
-       register_command(context, NULL, "fast",
-                       handle_fast_command, COMMAND_ANY,
-                       "fast <enable/disable> - place at beginning of "
-                       "config files. Sets defaults to fast and dangerous.");
+       Jim_DeleteAssocData(interp, "context");
 
        return context;
 }
 
-int command_context_mode(struct command_context *cmd_ctx, enum command_mode mode)
+void command_exit(struct command_context *context)
 {
-       if (!cmd_ctx)
-               return ERROR_INVALID_ARGUMENTS;
+       if (!context)
+               return;
 
-       cmd_ctx->mode = mode;
-       return ERROR_OK;
+       Jim_FreeInterp(context->interp);
+       free(context->help_list);
+       command_done(context);
 }
 
-void process_jim_events(void)
+int command_context_mode(struct command_context *cmd_ctx, enum command_mode mode)
 {
-#if !BUILD_ECOSBOARD
-       static int recursion = 0;
+       if (!cmd_ctx)
+               return ERROR_COMMAND_SYNTAX_ERROR;
 
-       if (!recursion)
-       {
-               recursion++;
-               Jim_ProcessEvents (interp, JIM_ALL_EVENTS | JIM_DONT_WAIT);
-               recursion--;
-       }
-#endif
+       cmd_ctx->mode = mode;
+       return ERROR_OK;
 }
 
-void register_jim(struct command_context *cmd_ctx, const char *name,
-               Jim_CmdProc cmd, const char *help)
+void process_jim_events(struct command_context *cmd_ctx)
 {
-       Jim_CreateCommand(interp, name, cmd, NULL, NULL);
-
-       Jim_Obj *cmd_list = Jim_NewListObj(interp, NULL, 0);
-       Jim_ListAppendElement(interp, cmd_list,
-                       Jim_NewStringObj(interp, name, -1));
-
-       command_helptext_add(cmd_list, help);
-}
+       static int recursion;
+       if (recursion)
+               return;
 
-/* return global variable long value or 0 upon failure */
-long jim_global_long(const char *variable)
-{
-       Jim_Obj *objPtr = Jim_GetGlobalVariableStr(interp, variable, JIM_ERRMSG);
-       long t;
-       if (Jim_GetLong(interp, objPtr, &t) == JIM_OK)
-       {
-               return t;
-       }
-       return 0;
+       recursion++;
+       Jim_ProcessEvents(cmd_ctx->interp, JIM_ALL_EVENTS | JIM_DONT_WAIT);
+       recursion--;
 }
 
 #define DEFINE_PARSE_NUM_TYPE(name, type, func, min, max) \
-       int parse##name(const char *str, type *ul) \
+       int parse ## name(const char *str, type * ul) \
        { \
-               if (!*str) \
-               { \
+               if (!*str) { \
                        LOG_ERROR("Invalid command argument"); \
                        return ERROR_COMMAND_ARGUMENT_INVALID; \
                } \
                char *end; \
+               errno = 0; \
                *ul = func(str, &end, 0); \
-               if (*end) \
-               { \
+               if (*end) { \
                        LOG_ERROR("Invalid command argument"); \
                        return ERROR_COMMAND_ARGUMENT_INVALID; \
                } \
-               if ((max == *ul) && (ERANGE == errno)) \
-               { \
-                       LOG_ERROR("Argument overflow"); \
-                       return ERROR_COMMAND_ARGUMENT_OVERFLOW; \
+               if ((max == *ul) && (errno == ERANGE)) { \
+                       LOG_ERROR("Argument overflow"); \
+                       return ERROR_COMMAND_ARGUMENT_OVERFLOW; \
                } \
-               if (min && (min == *ul) && (ERANGE == errno)) \
-               { \
+               if (min && (min == *ul) && (errno == ERANGE)) { \
                        LOG_ERROR("Argument underflow"); \
                        return ERROR_COMMAND_ARGUMENT_UNDERFLOW; \
                } \
                return ERROR_OK; \
        }
-DEFINE_PARSE_NUM_TYPE(_ulong, unsigned long , strtoul, 0, ULONG_MAX)
+DEFINE_PARSE_NUM_TYPE(_ulong, unsigned long, strtoul, 0, ULONG_MAX)
 DEFINE_PARSE_NUM_TYPE(_ullong, unsigned long long, strtoull, 0, ULLONG_MAX)
-DEFINE_PARSE_NUM_TYPE(_long, long , strtol, LONG_MIN, LONG_MAX)
+DEFINE_PARSE_NUM_TYPE(_long, long, strtol, LONG_MIN, LONG_MAX)
 DEFINE_PARSE_NUM_TYPE(_llong, long long, strtoll, LLONG_MIN, LLONG_MAX)
 
 #define DEFINE_PARSE_WRAPPER(name, type, min, max, functype, funcname) \
-       int parse##name(const char *str, type *ul) \
+       int parse ## name(const char *str, type * ul) \
        { \
                functype n; \
-               int retval = parse##funcname(str, &n); \
-               if (ERROR_OK != retval) \
+               int retval = parse ## funcname(str, &n); \
+               if (retval != ERROR_OK) \
                        return retval; \
                if (n > max) \
-                       return ERROR_COMMAND_ARGUMENT_OVERFLOW; \
+                       return ERROR_COMMAND_ARGUMENT_OVERFLOW; \
                if (min) \
                        return ERROR_COMMAND_ARGUMENT_UNDERFLOW; \
                *ul = n; \
                return ERROR_OK; \
        }
 
-#define DEFINE_PARSE_ULONG(name, type, min, max) \
-       DEFINE_PARSE_WRAPPER(name, type, min, max, unsigned long, _ulong)
-DEFINE_PARSE_ULONG(_uint, unsigned, 0, UINT_MAX)
-DEFINE_PARSE_ULONG(_u32, uint32_t, 0, UINT32_MAX)
-DEFINE_PARSE_ULONG(_u16, uint16_t, 0, UINT16_MAX)
-DEFINE_PARSE_ULONG(_u8, uint8_t, 0, UINT8_MAX)
+#define DEFINE_PARSE_ULONGLONG(name, type, min, max) \
+       DEFINE_PARSE_WRAPPER(name, type, min, max, unsigned long long, _ullong)
+DEFINE_PARSE_ULONGLONG(_uint, unsigned, 0, UINT_MAX)
+DEFINE_PARSE_ULONGLONG(_u64,  uint64_t, 0, UINT64_MAX)
+DEFINE_PARSE_ULONGLONG(_u32,  uint32_t, 0, UINT32_MAX)
+DEFINE_PARSE_ULONGLONG(_u16,  uint16_t, 0, UINT16_MAX)
+DEFINE_PARSE_ULONGLONG(_u8,   uint8_t,  0, UINT8_MAX)
+
+DEFINE_PARSE_ULONGLONG(_target_addr, target_addr_t, 0, TARGET_ADDR_MAX)
+
+#define DEFINE_PARSE_LONGLONG(name, type, min, max) \
+       DEFINE_PARSE_WRAPPER(name, type, min, max, long long, _llong)
+DEFINE_PARSE_LONGLONG(_int, int,     n < INT_MIN,   INT_MAX)
+DEFINE_PARSE_LONGLONG(_s64, int64_t, n < INT64_MIN, INT64_MAX)
+DEFINE_PARSE_LONGLONG(_s32, int32_t, n < INT32_MIN, INT32_MAX)
+DEFINE_PARSE_LONGLONG(_s16, int16_t, n < INT16_MIN, INT16_MAX)
+DEFINE_PARSE_LONGLONG(_s8,  int8_t,  n < INT8_MIN,  INT8_MAX)
+
+static int command_parse_bool(const char *in, bool *out,
+       const char *on, const char *off)
+{
+       if (strcasecmp(in, on) == 0)
+               *out = true;
+       else if (strcasecmp(in, off) == 0)
+               *out = false;
+       else
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       return ERROR_OK;
+}
 
-#define DEFINE_PARSE_LONG(name, type, min, max) \
-       DEFINE_PARSE_WRAPPER(name, type, min, max, long, _long)
-DEFINE_PARSE_LONG(_int, int, n < INT_MIN, INT_MAX)
-DEFINE_PARSE_LONG(_s32, int32_t, n < INT32_MIN, INT32_MAX)
-DEFINE_PARSE_LONG(_s16, int16_t, n < INT16_MIN, INT16_MAX)
-DEFINE_PARSE_LONG(_s8, int8_t, n < INT8_MIN, INT8_MAX)
+int command_parse_bool_arg(const char *in, bool *out)
+{
+       if (command_parse_bool(in, out, "on", "off") == ERROR_OK)
+               return ERROR_OK;
+       if (command_parse_bool(in, out, "enable", "disable") == ERROR_OK)
+               return ERROR_OK;
+       if (command_parse_bool(in, out, "true", "false") == ERROR_OK)
+               return ERROR_OK;
+       if (command_parse_bool(in, out, "yes", "no") == ERROR_OK)
+               return ERROR_OK;
+       if (command_parse_bool(in, out, "1", "0") == ERROR_OK)
+               return ERROR_OK;
+       return ERROR_COMMAND_SYNTAX_ERROR;
+}
+
+COMMAND_HELPER(handle_command_parse_bool, bool *out, const char *label)
+{
+       switch (CMD_ARGC) {
+               case 1: {
+                       const char *in = CMD_ARGV[0];
+                       if (command_parse_bool_arg(in, out) != ERROR_OK) {
+                               LOG_ERROR("%s: argument '%s' is not valid", CMD_NAME, in);
+                               return ERROR_COMMAND_SYNTAX_ERROR;
+                       }
+               }
+                       /* fallthrough */
+               case 0:
+                       LOG_INFO("%s is %s", label, *out ? "enabled" : "disabled");
+                       break;
+               default:
+                       return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+       return ERROR_OK;
+}