]> git.sur5r.net Git - openocd/blobdiff - src/helper/command.c
helper: hexify correctly handle signed chars
[openocd] / src / helper / command.c
index 42ff199bcc870a91589afcd437f2dbaa88e1dd80..bb04fb6a0708562b4e70b53be6a1620199fe4d36 100644 (file)
@@ -2,6 +2,12 @@
  *   Copyright (C) 2005 by Dominic Rath                                    *
  *   Dominic.Rath@gmx.de                                                   *
  *                                                                         *
+ *   Copyright (C) 2007,2008 Ã˜yvind Harboe                                 *
+ *   oyvind.harboe@zylin.com                                               *
+ *                                                                         *
+ *   Copyright (C) 2008, Duane Ellis                                       *
+ *   openocd@duaneeellis.com                                               *
+ *                                                                         *
  *   part of this file is taken from libcli (libcli.sourceforge.net)       *
  *   Copyright (C) David Parrish (david@dparrish.com)                      *
  *                                                                         *
  *   Free Software Foundation, Inc.,                                       *
  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
  ***************************************************************************/
+
 #ifdef HAVE_CONFIG_H
 #include "config.h"
 #endif
 
-#include "replacements.h"
+/* see Embedded-HOWTO.txt in Jim Tcl project hosted on BerliOS*/
+#define JIM_EMBEDDED
 
+/* @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"
+
+static int run_command(struct command_context *context,
+               struct command *c, const char *words[], unsigned num_words);
+
+struct log_capture_state {
+       Jim_Interp *interp;
+       Jim_Obj *output;
+};
 
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <unistd.h>
+static void tcl_output(void *privData, const char *file, unsigned line,
+       const char *function, const char *string)
+{
+       struct log_capture_state *state = (struct log_capture_state *)privData;
+       Jim_AppendString(state->interp, state->output, string, strlen(string));
+}
 
-int fast_and_dangerous = 0;
+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 *tclOutput = Jim_NewStringObj(interp, "", 0);
+       if (NULL == tclOutput)
+               return NULL;
 
-void command_print_help_line(command_context_t* context, struct command_s *command, int indent);
+       struct log_capture_state *state = malloc(sizeof(*state));
+       if (NULL == state)
+               return NULL;
+
+       state->interp = interp;
+       Jim_IncrRefCount(tclOutput);
+       state->output = tclOutput;
 
-int handle_sleep_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc);
-int handle_time_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc);
-int handle_fast_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc);
+       log_add_callback(tcl_output, state);
 
-/* forward declaration of jim_command */
-extern int jim_command(command_context_t *context, char *line);
+       return state;
+}
 
-int build_unique_lengths(command_context_t *context, command_t *commands)
+/* 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)
 {
-       command_t *c, *p;
+       if (NULL == state)
+               return;
 
-       /* iterate through all commands */
-       for (c = commands; c; c = c->next)
-       {
-               /* find out how many characters are required to uniquely identify a command */
-               for (c->unique_len = 1; c->unique_len <= strlen(c->name); c->unique_len++)
-               {
-                       int foundmatch = 0;
-                       
-                       /* for every command, see if the current length is enough */
-                       for (p = commands; p; p = p->next)
-                       {
-                               /* ignore the command itself */
-                               if (c == p)
-                                       continue;
-                               
-                               /* compare commands up to the current length */
-                               if (strncmp(p->name, c->name, c->unique_len) == 0)
-                                       foundmatch++;
-                       }
-                       
-                       /* when none of the commands matched, we've found the minimum length required */
-                       if (!foundmatch)
-                               break;
+       log_remove_callback(tcl_output, state);
+
+       int length;
+       Jim_GetString(state->output, &length);
+
+       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)
+               *return_retval = retval;
+
+       return (retval == ERROR_OK) ? JIM_OK : JIM_ERR;
+}
+
+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 */
+void script_debug(Jim_Interp *interp, const char *name,
+       unsigned argc, Jim_Obj * const *argv)
+{
+       if (debug_level < LOG_LVL_DEBUG)
+               return;
+
+       char *dbg = alloc_printf("command - %s", name);
+       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 script_command_args_free(const char **words, unsigned nwords)
+{
+       for (unsigned i = 0; i < nwords; i++)
+               free((void *)words[i]);
+       free(words);
+}
+
+static const char **script_command_args_alloc(
+       unsigned argc, Jim_Obj * const *argv, unsigned *nwords)
+{
+       const char **words = malloc(argc * sizeof(char *));
+       if (NULL == words)
+               return NULL;
+
+       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] == NULL) {
+                       script_command_args_free(words, i);
+                       return NULL;
                }
-               
-               /* if the current command has children, build the unique lengths for them */
-               if (c->children)
-                       build_unique_lengths(context, c->children);
        }
-       
-       return ERROR_OK;
+       *nwords = i;
+       return words;
+}
+
+struct command_context *current_command_context(Jim_Interp *interp)
+{
+       /* grab the command context from the associated data */
+       struct command_context *cmd_ctx = Jim_GetAssocData(interp, "context");
+       if (NULL == 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 cmd_ctx;
+}
+
+static int script_command_run(Jim_Interp *interp,
+       int argc, Jim_Obj * const *argv, struct command *c, bool capture)
+{
+       target_call_timer_callbacks_now();
+       LOG_USER_N("%s", "");   /* Keep GDB connection alive*/
+
+       unsigned nwords;
+       const char **words = script_command_args_alloc(argc, argv, &nwords);
+       if (NULL == words)
+               return JIM_ERR;
+
+       struct log_capture_state *state = NULL;
+       if (capture)
+               state = command_log_capture_start(interp);
+
+       struct command_context *cmd_ctx = current_command_context(interp);
+       int retval = run_command(cmd_ctx, c, (const char **)words, nwords);
+
+       command_log_capture_finish(state);
+
+       script_command_args_free(words, nwords);
+       return command_retval_set(interp, retval);
+}
+
+static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+       /* the private data is stashed in the interp structure */
+
+       struct command *c = interp->cmdPrivData;
+       assert(c);
+       script_debug(interp, c->name, argc, argv);
+       return script_command_run(interp, argc, argv, c, true);
+}
+
+static struct command *command_root(struct command *c)
+{
+       while (NULL != c->parent)
+               c = c->parent;
+       return c;
 }
 
-/* Avoid evaluating this each time we add a command. Reduces overhead from O(n^2) to O(n). 
- * Makes a difference on ARM7 types machines and is not observable on GHz machines.
+/**
+ * Find a command by name from a list of commands.
+ * @returns Returns the named command if it exists in the list.
+ * Returns NULL otherwise.
  */
-static int unique_length_dirty = 1; 
+static struct command *command_find(struct command *head, const char *name)
+{
+       for (struct command *cc = head; cc; cc = cc->next) {
+               if (strcmp(cc->name, name) == 0)
+                       return cc;
+       }
+       return NULL;
+}
 
-command_t* register_command(command_context_t *context, command_t *parent, char *name, int (*handler)(struct command_context_s *context, char* name, char** args, int argc), enum command_mode mode, char *help)
+struct command *command_find_in_context(struct command_context *cmd_ctx,
+       const char *name)
 {
-       command_t *c, *p;
-       unique_length_dirty = 1;
-       
-       if (!context || !name)
+       return command_find(cmd_ctx->commands, name);
+}
+struct command *command_find_in_parent(struct command *parent,
+       const char *name)
+{
+       return command_find(parent->children, name);
+}
+
+/**
+ * Add the command into the linked list, sorted by name.
+ * @param head Address to head of command list pointer, which may be
+ * updated if @c c gets inserted at the beginning of the list.
+ * @param c The command to add to the list pointed to by @c head.
+ */
+static void command_add_child(struct command **head, struct command *c)
+{
+       assert(head);
+       if (NULL == *head) {
+               *head = c;
+               return;
+       }
+
+       while ((*head)->next && (strcmp(c->name, (*head)->name) > 0))
+               head = &(*head)->next;
+
+       if (strcmp(c->name, (*head)->name) > 0) {
+               c->next = (*head)->next;
+               (*head)->next = c;
+       } else {
+               c->next = *head;
+               *head = c;
+       }
+}
+
+static struct command **command_list_for_parent(
+       struct command_context *cmd_ctx, struct command *parent)
+{
+       return parent ? &parent->children : &cmd_ctx->commands;
+}
+
+static void command_free(struct command *c)
+{
+       /** @todo if command has a handler, unregister its jim command! */
+
+       while (NULL != c->children) {
+               struct command *tmp = c->children;
+               c->children = tmp->next;
+               command_free(tmp);
+       }
+
+       if (c->name)
+               free((void *)c->name);
+       if (c->help)
+               free((void *)c->help);
+       if (c->usage)
+               free((void *)c->usage);
+       free(c);
+}
+
+static struct command *command_new(struct command_context *cmd_ctx,
+       struct command *parent, const struct command_registration *cr)
+{
+       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 == NULL) && (cr->usage == NULL)) {
+               LOG_DEBUG("BUG: command '%s%s%s' does not have the "
+                       "'.usage' field filled out",
+                       parent && parent->name ? parent->name : "",
+                       parent && parent->name ? " " : "",
+                       cr->name);
+       }
+
+       struct command *c = calloc(1, sizeof(struct command));
+       if (NULL == c)
                return NULL;
-                               
-       c = malloc(sizeof(command_t));
-       
-       c->name = strdup(name);
+
+       c->name = strdup(cr->name);
+       if (cr->help)
+               c->help = strdup(cr->help);
+       if (cr->usage)
+               c->usage = strdup(cr->usage);
+
+       if (!c->name || (cr->help && !c->help) || (cr->usage && !c->usage))
+               goto command_new_error;
+
        c->parent = parent;
-       c->children = NULL;
-       c->handler = handler;
-       c->mode = mode;
-       if (help)
-               c->help = strdup(help);
-       else
-               c->help = NULL;
-       c->unique_len = 0;
-       c->next = NULL;
-       
-       /* place command in tree */
-       if (parent)
-       {
-               if (parent->children)
-               {
-                       /* find last child */
-                       for (p = parent->children; p && p->next; p = p->next);
-                       if (p)
-                               p->next = c;
-               }
-               else
-               {
-                       parent->children = c;
-               }
+       c->handler = cr->handler;
+       c->jim_handler = cr->jim_handler;
+       c->jim_handler_data = cr->jim_handler_data;
+       c->mode = cr->mode;
+
+       command_add_child(command_list_for_parent(cmd_ctx, parent), c);
+
+       return c;
+
+command_new_error:
+       command_free(c);
+       return NULL;
+}
+
+static int command_unknown(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
+
+static int register_command_handler(struct command_context *cmd_ctx,
+       struct command *c)
+{
+       Jim_Interp *interp = cmd_ctx->interp;
+       const char *ocd_name = alloc_printf("ocd_%s", c->name);
+       if (NULL == ocd_name)
+               return JIM_ERR;
+
+       LOG_DEBUG("registering '%s'...", ocd_name);
+
+       Jim_CmdProc func = c->handler ? &script_command : &command_unknown;
+       int retval = Jim_CreateCommand(interp, ocd_name, func, c, NULL);
+       free((void *)ocd_name);
+       if (JIM_OK != retval)
+               return retval;
+
+       /* we now need to add an overrideable proc */
+       const char *override_name = alloc_printf(
+                       "proc %s {args} {eval ocd_bouncer %s $args}",
+                       c->name, c->name);
+       if (NULL == override_name)
+               return JIM_ERR;
+
+       retval = Jim_Eval_Named(interp, override_name, 0, 0);
+       free((void *)override_name);
+
+       return retval;
+}
+
+struct command *register_command(struct command_context *context,
+       struct command *parent, const struct command_registration *cr)
+{
+       if (!context || !cr->name)
+               return NULL;
+
+       const char *name = cr->name;
+       struct command **head = command_list_for_parent(context, parent);
+       struct command *c = command_find(*head, name);
+       if (NULL != 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 in '%s' context",
+                       name, parent ? parent->name : "<global>");
+               return c;
        }
-       else
-       {
-               if (context->commands)
-               {
-                       /* find last command */
-                       for (p = context->commands; p && p->next; p = p->next);
-                       if (p)
-                               p->next = c;
-               }
-               else
-               {
-                       context->commands = c;
-               }
+
+       c = command_new(context, parent, cr);
+       if (NULL == c)
+               return NULL;
+
+       int retval = ERROR_OK;
+       if (NULL != cr->jim_handler && NULL == parent) {
+               retval = Jim_CreateCommand(context->interp, cr->name,
+                               cr->jim_handler, cr->jim_handler_data, NULL);
+       } else if (NULL != cr->handler || NULL != parent)
+               retval = register_command_handler(context, command_root(c));
+
+       if (ERROR_OK != retval) {
+               unregister_command(context, parent, name);
+               c = NULL;
        }
-       
        return c;
 }
 
-int unregister_all_commands(command_context_t *context)
+int register_commands(struct command_context *cmd_ctx, struct command *parent,
+       const struct command_registration *cmds)
 {
-       command_t *c, *c2;
-       
-       unique_length_dirty = 1;
-       
-       if (context == NULL)
-               return ERROR_OK;
-       
-       
-       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->help);
-                       c2->help = NULL;
-                       free(c2);
-                       c2 = NULL;
+       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 (NULL != cr->name) {
+                       c = register_command(cmd_ctx, parent, cr);
+                       if (NULL == c) {
+                               retval = ERROR_FAIL;
+                               break;
+                       }
                }
-               
-               context->commands = context->commands->next;
-               
-               free(c->name);
-               c->name = NULL;
-               free(c->help);
-               c->help = NULL;
-               free(c);
-               c = NULL;               
-       }
-       
-       return ERROR_OK;
+               if (NULL != cr->chain) {
+                       struct command *p = c ? : parent;
+                       retval = register_commands(cmd_ctx, p, cr->chain);
+                       if (ERROR_OK != retval)
+                               break;
+               }
+       }
+       if (ERROR_OK != retval) {
+               for (unsigned j = 0; j < i; j++)
+                       unregister_command(cmd_ctx, parent, cmds[j].name);
+       }
+       return retval;
 }
 
-int unregister_command(command_context_t *context, char *name)
+int unregister_all_commands(struct command_context *context,
+       struct command *parent)
 {
-       command_t *c, *p = NULL, *c2;
-       
-       unique_length_dirty = 1;
-       
-       if ((!context) || (!name))
-               return ERROR_INVALID_ARGUMENTS;
-       
-       /* find command */
-       for (c = context->commands; c; c = c->next)
-       {
-               if (strcmp(name, c->name) == 0)
-               {
-                       /* unlink command */
-                       if (p)
-                       {
-                               p->next = c->next;
-                       }
-                       else
-                       {
-                               context->commands = c->next;
-                       }
-                       
-                       /* unregister children */
-                       if (c->children)
-                       {
-                               for (c2 = c->children; c2; c2 = c2->next)
-                               {
-                                       free(c2->name);
-                                       if (c2->help)
-                                               free(c2->help);
-                                       free(c2);
-                               }
-                       }
-                       
-                       /* delete command */
-                       free(c->name);
-                       if (c->help)
-                               free(c->help);
-                       free(c);
-               }
-               
-               /* remember the last command for unlinking */
-               p = c;
+       if (context == NULL)
+               return ERROR_OK;
+
+       struct command **head = command_list_for_parent(context, parent);
+       while (NULL != *head) {
+               struct command *tmp = *head;
+               *head = tmp->next;
+               command_free(tmp);
        }
-       
+
        return ERROR_OK;
 }
 
-int parse_line(char *line, char *words[], int max_words)
+int unregister_command(struct command_context *context,
+       struct command *parent, const char *name)
 {
-       int nwords = 0;
-       char *p = line;
-       char *word_start = line;
-       int inquote = 0;
+       if ((!context) || (!name))
+               return ERROR_COMMAND_SYNTAX_ERROR;
 
-       while (nwords < max_words - 1)
-       {
-               /* check if we reached
-                * a terminating NUL
-                * a matching closing quote character " or '
-                * we're inside a word but not a quote, and the current character is whitespace
-                */
-               if (!*p || *p == inquote || (word_start && !inquote && isspace(*p)))
-               {
-                       /* we're inside a word or quote, and reached its end*/
-                       if (word_start)
-                       {
-                               int len;
-                               char *word_end=p;
-                               
-                               /* This will handle extra whitespace within quotes */
-                               while (isspace(*word_start)&&(word_start<word_end))
-                                       word_start++;
-                               while (isspace(*(word_end-1))&&(word_start<word_end))
-                                       word_end--;
-                               len = word_end - word_start;
-                               
-                               if (len>0)
-                               {
-                                       /* copy the word */
-                                       memcpy(words[nwords] = malloc(len + 1), word_start, len);
-                                       /* add terminating NUL */
-                                       words[nwords++][len] = 0;
-                               }
-                       }
-                       /* we're done parsing the line */
-                       if (!*p)
-                               break;
+       struct command *p = NULL;
+       struct command **head = command_list_for_parent(context, parent);
+       for (struct command *c = *head; NULL != c; p = c, c = c->next) {
+               if (strcmp(name, c->name) != 0)
+                       continue;
 
-                       /* skip over trailing quote or whitespace*/
-                       if (inquote || isspace(*p))
-                               p++;
-                       while (isspace(*p))
-                               p++;
-                       
-                       inquote = 0;
-                       word_start = 0;
-               }
-               else if (*p == '"' || *p == '\'')
-               {
-                       /* we've reached the beginning of a quote */
-                       inquote = *p++;
-                       word_start = p;
-               }
+               if (p)
+                       p->next = c->next;
                else
-               {
-                       /* we've reached the beginning of a new word */
-                       if (!word_start)
-                               word_start = p;
-                       
-                       /* normal character, skip */
-                       p++;
-               }
+                       *head = c->next;
+
+               command_free(c);
+               return ERROR_OK;
        }
-       
-       return nwords;
+
+       return ERROR_OK;
 }
 
-void command_output_text(command_context_t *context, const char *data)
+void command_set_handler_data(struct command *c, void *p)
 {
-       if( context && context->output_handler && data  ){
-               context->output_handler( context, data );
-       }
+       if (NULL != c->handler || NULL != c->jim_handler)
+               c->jim_handler_data = p;
+       for (struct command *cc = c->children; NULL != cc; cc = cc->next)
+               command_set_handler_data(cc, p);
 }
 
-void command_print_n(command_context_t *context, char *format, ...)
+void command_output_text(struct command_context *context, const char *data)
+{
+       if (context && context->output_handler && data)
+               context->output_handler(context, data);
+}
+
+void command_print_sameline(struct command_context *context, const char *format, ...)
 {
        char *string;
-       
+
        va_list ap;
        va_start(ap, format);
 
        string = alloc_vprintf(format, ap);
-       if (string != NULL)
-       {
-               context->output_handler(context, string);
+       if (string != NULL) {
+               /* 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); */
                free(string);
        }
 
        va_end(ap);
 }
 
-void command_print(command_context_t *context, char *format, ...)
+void command_print(struct command_context *context, const char *format, ...)
 {
        char *string;
 
@@ -337,391 +538,929 @@ void command_print(command_context_t *context, 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 */
-               context->output_handler(context, string);
+       if (string != NULL) {
+               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); */
                free(string);
        }
 
        va_end(ap);
 }
 
-command_t *find_command(command_context_t *context, command_t *commands, char *words[], int num_words, int start_word, int *new_start_word)
+static char *__command_name(struct command *c, char delim, unsigned extra)
 {
-       command_t *c;
-       
-       if (unique_length_dirty)
-       {
-               unique_length_dirty = 0;
-               /* update unique lengths */
-               build_unique_lengths(context, context->commands);
+       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);
        }
-       
-       for (c = commands; c; c = c->next)
-       {
-               if (strncasecmp(c->name, words[start_word], c->unique_len))
-                       continue;
+       return name;
+}
 
-               if (strncasecmp(c->name, words[start_word], strlen(words[start_word])))
-                       continue;
-               
-               if ((context->mode == COMMAND_CONFIG) || (c->mode == COMMAND_ANY) || (c->mode == context->mode) )
-               {
-                       if (!c->children)
-                       {
-                               if (!c->handler)
-                               {
-                                       return NULL;
-                               }
-                               else
-                               {
-                                       *new_start_word=start_word;
-                                       return c;
-                               }
-                       }
-                       else
-                       {
-                               if (start_word == num_words - 1)
-                               {
-                                       return NULL;
-                               }
-                               return find_command(context, c->children, words, num_words, start_word + 1, new_start_word);
-                       }
-               }
-       }
-       return NULL;
+char *command_name(struct command *c, char delim)
+{
+       return __command_name(c, delim, 0);
 }
 
-int find_and_run_command(command_context_t *context, command_t *commands, char *words[], int num_words)
+static bool command_can_run(struct command_context *cmd_ctx, struct command *c)
 {
-       int start_word=0;
-       command_t *c;
-       c = find_command(context, commands, words, num_words, start_word, &start_word);
-       if (c == NULL)
-       {
-               /* just return command not found */
-               return ERROR_COMMAND_NOTFOUND;
-       }
-       
-       int retval = c->handler(context, c->name, words + start_word + 1, num_words - start_word - 1);
-       if (retval == ERROR_COMMAND_SYNTAX_ERROR)
-       {
-               command_print(context, "Syntax error:");
-               command_print_help_line(context, c, 0);
+       return c->mode == COMMAND_ANY || c->mode == cmd_ctx->mode;
+}
+
+static int run_command(struct command_context *context,
+       struct command *c, const char *words[], unsigned num_words)
+{
+       if (!command_can_run(context, c)) {
+               /* 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;
+               }
+               LOG_ERROR("The '%s' command must be used %s 'init'.",
+                       c->name, when);
+               return ERROR_FAIL;
        }
-       else if (retval == ERROR_COMMAND_CLOSE_CONNECTION)
-       {
+
+       struct command_invocation cmd = {
+               .ctx = context,
+               .current = c,
+               .name = c->name,
+               .argc = num_words - 1,
+               .argv = words + 1,
+       };
+       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, "usage %s", full_name);
+                       free(full_name);
+               } else
+                       retval = -ENOMEM;
+       } else if (retval == ERROR_COMMAND_CLOSE_CONNECTION) {
                /* just fall through for a shutdown request */
-       }
-       else if (retval != ERROR_OK)
-       {
+       } 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); 
+               LOG_DEBUG("Command failed with error code %d", retval);
        }
-       
-       return retval; 
+
+       return retval;
 }
 
-int command_run_line_internal(command_context_t *context, char *line)
+int command_run_line(struct command_context *context, char *line)
 {
-       LOG_USER_N("%s", ""); /* Keep GDB connection alive*/ 
-       
-       int nwords;
-       char *words[128] = {0};
-       int retval;
-       int i;
-
-       /* skip preceding whitespace */
-       while (isspace(*line))
-               line++;
-       
-       /* empty line, ignore */
-       if (!*line)
-               return ERROR_OK;
-       
-       /* ignore comments */
-       if (*line && (line[0] == '#'))
-               return ERROR_OK;
-       
-       LOG_DEBUG("%s", line);
+       /* all the parent commands have been registered with the interpreter
+        * so, can just evaluate the line as a script and check for
+        * results
+        */
+       /* run the line thru a script engine */
+       int retval = ERROR_FAIL;
+       int retcode;
+       /* Beware! This code needs to be reentrant. It is also possible
+        * for OpenOCD commands to be invoked directly from Tcl. This would
+        * happen when the Jim Tcl interpreter is provided by eCos for
+        * instance.
+        */
+       Jim_Interp *interp = context->interp;
+       Jim_DeleteAssocData(interp, "context");
+       retcode = Jim_SetAssocData(interp, "context", NULL, context);
+       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, 0, 0);
 
-       nwords = parse_line(line, words, sizeof(words) / sizeof(words[0]));
-       
-       if (nwords > 0)
-       {
-               retval = find_and_run_command(context, context->commands, words, nwords);
+                       Jim_DeleteAssocData(interp, "retval");
+               }
+               Jim_DeleteAssocData(interp, "context");
+       }
+       if (retcode == JIM_ERR) {
+               if (retval != ERROR_COMMAND_CLOSE_CONNECTION) {
+                       /* We do not print the connection closed error message */
+                       Jim_MakeErrorMessage(interp);
+                       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;
+       } else if (retcode == JIM_EXIT) {
+               /* ignore.
+                * exit(Jim_GetExitCode(interp)); */
+       } else {
+               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("\n");
+               }
+               retval = ERROR_OK;
        }
-       else
-               return ERROR_INVALID_ARGUMENTS;
-       
-       for (i = 0; i < nwords; i++)
-               free(words[i]);
-       
        return retval;
 }
 
-int command_run_line(command_context_t *context, char *line)
+int command_run_linef(struct command_context *context, const char *format, ...)
 {
-       int retval;
-       
-       if ((!context) || (!line))
-               return ERROR_INVALID_ARGUMENTS;
-       
-       if ((retval = command_run_line_internal(context, line)) == ERROR_COMMAND_NOTFOUND)
-       {
-               /* If we can't find a command, then try the interpreter. 
-                * If there is no interpreter implemented, then this will
-                * simply print a syntax error.
-                * 
-                * These hooks were left in to reduce patch size for 
-                * wip to add scripting language.
-                */
-               
-               return jim_command(context, line);
+       int retval = ERROR_FAIL;
+       char *string;
+       va_list ap;
+       va_start(ap, format);
+       string = alloc_vprintf(format, ap);
+       if (string != NULL) {
+               retval = command_run_line(context, string);
+               free(string);
        }
-       
+       va_end(ap);
        return retval;
 }
 
-int command_run_file(command_context_t *context, FILE *file, enum command_mode mode)
+void command_set_output_handler(struct command_context *context,
+       command_output_handler_t output_handler, void *priv)
 {
-       int retval = ERROR_OK;
-       int old_command_mode;
-       char *buffer=malloc(4096);
-       if (buffer==NULL)
-       {
-               return ERROR_INVALID_ARGUMENTS;
-       }
-       
-       old_command_mode = context->mode;
-       context->mode = mode;
-       
-       while (fgets(buffer, 4096, file))
-       {
-               char *p;
-               char *cmd, *end;
-               
-               /* stop processing line after a comment (#, !) or a LF, CR were encountered */
-               if ((p = strpbrk(buffer, "#!\r\n")))
-                       *p = 0;
-
-               /* skip over leading whitespace */
-               cmd = buffer;
-               while (isspace(*cmd))
-                       cmd++;
-
-               /* empty (all whitespace) line? */
-               if (!*cmd)
-                       continue;
-               
-               /* search the end of the current line, ignore trailing whitespace */
-               for (p = end = cmd; *p; p++)
-                       if (!isspace(*p))
-                               end = p;
-               
-               /* terminate end */
-               *++end = 0;
-               if (strcasecmp(cmd, "quit") == 0)
-                       break;
+       context->output_handler = output_handler;
+       context->output_handler_priv = priv;
+}
 
-               /* run line */
-               if ((retval = command_run_line(context, cmd)) == ERROR_COMMAND_CLOSE_CONNECTION)
-                       break;
+struct command_context *copy_command_context(struct command_context *context)
+{
+       struct command_context *copy_context = malloc(sizeof(struct command_context));
+
+       *copy_context = *context;
+
+       return copy_context;
+}
+
+void command_done(struct command_context *cmd_ctx)
+{
+       if (NULL == cmd_ctx)
+               return;
+
+       free(cmd_ctx);
+}
+
+/* find full path to file */
+static int jim_find(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+       if (argc != 2)
+               return JIM_ERR;
+       const char *file = Jim_GetString(argv[1], NULL);
+       char *full_path = find_file(file);
+       if (full_path == NULL)
+               return JIM_ERR;
+       Jim_Obj *result = Jim_NewStringObj(interp, full_path, strlen(full_path));
+       free(full_path);
+
+       Jim_SetResult(interp, result);
+       return JIM_OK;
+}
+
+COMMAND_HANDLER(jim_echo)
+{
+       if (CMD_ARGC == 2 && !strcmp(CMD_ARGV[0], "-n")) {
+               LOG_USER_N("%s", CMD_ARGV[1]);
+               return JIM_OK;
        }
-       
-       context->mode = old_command_mode;
+       if (CMD_ARGC != 1)
+               return JIM_ERR;
+       LOG_USER("%s", CMD_ARGV[0]);
+       return JIM_OK;
+}
 
-       
-       free(buffer);
-       
-       return retval;
+/* 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 = jtag_poll_get_enabled();
+
+       jtag_poll_set_enabled(false);
+
+       const char *str = Jim_GetString(argv[1], NULL);
+       int retcode = Jim_Eval_Named(interp, str, __THIS__FILE__, __LINE__);
+
+       jtag_poll_set_enabled(save_poll);
+
+       command_log_capture_finish(state);
+
+       return retcode;
 }
 
-int command_run_linef(command_context_t *context, char *format, ...)
+static COMMAND_HELPER(command_help_find, struct command *head,
+       struct command **out)
 {
-       int retval=ERROR_FAIL;
-       char *string;
-       va_list ap;
-       va_start(ap, format);
-       string = alloc_vprintf(format, ap);
-       if (string!=NULL)
-       {
-               retval=command_run_line(context, string);
+       if (0 == CMD_ARGC)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       *out = command_find(head, CMD_ARGV[0]);
+       if (NULL == *out && strncmp(CMD_ARGV[0], "ocd_", 4) == 0)
+               *out = command_find(head, CMD_ARGV[0] + 4);
+       if (NULL == *out)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       if (--CMD_ARGC == 0)
+               return ERROR_OK;
+       CMD_ARGV++;
+       return CALL_COMMAND_HANDLER(command_help_find, (*out)->children, out);
+}
+
+static COMMAND_HELPER(command_help_show, struct command *c, unsigned n,
+       bool show_help, const char *match);
+
+static COMMAND_HELPER(command_help_show_list, struct command *head, unsigned n,
+       bool show_help, const char *match)
+{
+       for (struct command *c = head; NULL != c; c = c->next)
+               CALL_COMMAND_HANDLER(command_help_show, c, n, show_help, match);
+       return ERROR_OK;
+}
+
+#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;
        }
-       va_end(ap);
+}
+
+static COMMAND_HELPER(command_help_show, struct command *c, unsigned n,
+       bool show_help, const char *match)
+{
+       char *cmd_name = command_name(c, ' ');
+       if (NULL == cmd_name)
+               return -ENOMEM;
+
+       /* If the match string occurs anywhere, we print out
+        * stuff for this command. */
+       bool is_match = (strstr(cmd_name, match) != NULL) ||
+               ((c->usage != NULL) && (strstr(c->usage, match) != NULL)) ||
+               ((c->help != NULL) && (strstr(c->help, match) != NULL));
+
+       if (is_match) {
+               command_help_show_indent(n);
+               LOG_USER_N("%s", cmd_name);
+       }
+       free(cmd_name);
+
+       if (is_match) {
+               if (c->usage) {
+                       LOG_USER_N(" ");
+                       command_help_show_wrap(c->usage, 0, n + 5);
+               } else
+                       LOG_USER_N("\n");
+       }
+
+       if (is_match && show_help) {
+               char *msg;
+
+               /* Normal commands are runtime-only; highlight exceptions */
+               if (c->mode != COMMAND_EXEC) {
+                       const char *stage_msg = "";
+
+                       switch (c->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 ? : "", stage_msg);
+               } else
+                       msg = alloc_printf("%s", c->help ? : "");
+
+               if (NULL != msg) {
+                       command_help_show_wrap(msg, n + 3, n + 3);
+                       free(msg);
+               } else
+                       return -ENOMEM;
+       }
+
+       if (++n > 5) {
+               LOG_ERROR("command recursion exceeded");
+               return ERROR_FAIL;
+       }
+
+       return CALL_COMMAND_HANDLER(command_help_show_list,
+               c->children, n, show_help, match);
+}
+
+COMMAND_HANDLER(handle_help_command)
+{
+       bool full = strcmp(CMD_NAME, "help") == 0;
+       int retval;
+       struct command *c = CMD_CTX->commands;
+       char *match = NULL;
+
+       if (CMD_ARGC == 0)
+               match = "";
+       else if (CMD_ARGC >= 1) {
+               unsigned i;
+
+               for (i = 0; i < CMD_ARGC; ++i) {
+                       if (NULL != match) {
+                               char *prev = match;
+
+                               match = alloc_printf("%s %s", match, CMD_ARGV[i]);
+                               free(prev);
+                               if (NULL == match) {
+                                       LOG_ERROR("unable to build search string");
+                                       return -ENOMEM;
+                               }
+                       } else {
+                               match = alloc_printf("%s", CMD_ARGV[i]);
+                               if (NULL == match) {
+                                       LOG_ERROR("unable to build search string");
+                                       return -ENOMEM;
+                               }
+                       }
+               }
+       } else
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       retval = CALL_COMMAND_HANDLER(command_help_show_list,
+                       c, 0, full, match);
+
+       if (CMD_ARGC >= 1)
+               free(match);
        return retval;
 }
 
-void command_print_help_line(command_context_t* context, struct command_s *command, int indent)
+static int command_unknown_find(unsigned argc, Jim_Obj *const *argv,
+       struct command *head, struct command **out, bool top_level)
 {
-       command_t *c;
-       char *indent_text=malloc(indent + 2);
-       
-       char *help = "no help available";
-       char name_buf[64];
-       
-       if (indent)
-       {
-           indent_text[0] = ' ';
-           memset(indent_text + 1, '-', indent);
-           indent_text[indent + 1] = 0;
-       }
-       
-       if (command->help)
-               help = command->help;
-               
-       snprintf(name_buf, 64, command->name);
-
-       if (indent)
-           strncat(name_buf, indent_text, 64);
-
-       command_print(context, "%20s\t%s", name_buf, help, indent);
-       
-       if (command->children)
-       {
-               for (c = command->children; c; c = c->next)
-               {
-                       command_print_help_line(context, c, indent + 1);
+       if (0 == argc)
+               return argc;
+       const char *cmd_name = Jim_GetString(argv[0], NULL);
+       struct command *c = command_find(head, cmd_name);
+       if (NULL == c && top_level && strncmp(cmd_name, "ocd_", 4) == 0)
+               c = command_find(head, cmd_name + 4);
+       if (NULL == c)
+               return argc;
+       *out = c;
+       return command_unknown_find(--argc, ++argv, (*out)->children, out, false);
+}
+
+static int command_unknown(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+       const char *cmd_name = Jim_GetString(argv[0], NULL);
+       if (strcmp(cmd_name, "unknown") == 0) {
+               if (argc == 1)
+                       return JIM_OK;
+               argc--;
+               argv++;
+       }
+       script_debug(interp, cmd_name, argc, argv);
+
+       struct command_context *cmd_ctx = current_command_context(interp);
+       struct command *c = cmd_ctx->commands;
+       int remaining = command_unknown_find(argc, argv, c, &c, true);
+       /* if nothing could be consumed, then it's really an unknown command */
+       if (remaining == argc) {
+               const char *cmd = Jim_GetString(argv[0], NULL);
+               LOG_ERROR("Unknown command:\n  %s", cmd);
+               return JIM_OK;
+       }
+
+       bool found = true;
+       Jim_Obj *const *start;
+       unsigned count;
+       if (c->handler || c->jim_handler) {
+               /* include the command name in the list */
+               count = remaining + 1;
+               start = argv + (argc - remaining - 1);
+       } else {
+               c = command_find(cmd_ctx->commands, "usage");
+               if (NULL == c) {
+                       LOG_ERROR("unknown command, but usage is missing too");
+                       return JIM_ERR;
                }
+               count = argc - remaining;
+               start = argv;
+               found = false;
        }
-       free(indent_text);
+       /* pass the command through to the intended handler */
+       if (c->jim_handler) {
+               interp->cmdPrivData = c->jim_handler_data;
+               return (*c->jim_handler)(interp, count, start);
+       }
+
+       return script_command_run(interp, count, start, c, found);
 }
 
-int command_print_help_match(command_context_t* context, command_t* c_first, char* name, char** args, int argc)
+static int jim_command_mode(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 {
-       command_t * c;
+       struct command_context *cmd_ctx = current_command_context(interp);
+       enum command_mode mode;
 
-       for (c = c_first; c; c = c->next)
-       {
-               if (argc > 0)
-               {
-                       if (strncasecmp(c->name, args[0], c->unique_len))
-                               continue;
-
-                       if (strncasecmp(c->name, args[0], strlen(args[0])))
-                               continue;
-
-                       if (argc > 1)
-                       {
-                               command_print_help_match(context, c->children, name, args + 1, argc - 1);
-                               continue;
-                       }
+       if (argc > 1) {
+               struct command *c = cmd_ctx->commands;
+               int remaining = command_unknown_find(argc - 1, argv + 1, c, &c, true);
+               /* if nothing could be consumed, then it's an unknown command */
+               if (remaining == argc - 1) {
+                       Jim_SetResultString(interp, "unknown", -1);
+                       return JIM_OK;
                }
+               mode = c->mode;
+       } else
+               mode = cmd_ctx->mode;
 
-               command_print_help_line(context, c, 0);
+       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;
        }
-       
-       return ERROR_OK;
+       Jim_SetResultString(interp, mode_str, -1);
+       return JIM_OK;
 }
 
-int command_print_help(command_context_t* context, char* name, char** args, int argc)
+static int jim_command_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
 {
-    return command_print_help_match(context, context->commands, name, args, argc);
+       if (1 == argc)
+               return JIM_ERR;
+
+       struct command_context *cmd_ctx = current_command_context(interp);
+       struct command *c = cmd_ctx->commands;
+       int remaining = command_unknown_find(argc - 1, argv + 1, c, &c, true);
+       /* if nothing could be consumed, then it's an unknown command */
+       if (remaining == argc - 1) {
+               Jim_SetResultString(interp, "unknown", -1);
+               return JIM_OK;
+       }
+
+       if (c->jim_handler)
+               Jim_SetResultString(interp, "native", -1);
+       else if (c->handler)
+               Jim_SetResultString(interp, "simple", -1);
+       else
+               Jim_SetResultString(interp, "group", -1);
+
+       return JIM_OK;
 }
 
-void command_set_output_handler(command_context_t* context, int (*output_handler)(struct command_context_s *context, const char* line), void *priv)
+int help_add_command(struct command_context *cmd_ctx, struct command *parent,
+       const char *cmd_name, const char *help_text, const char *usage)
 {
-       context->output_handler = output_handler;
-       context->output_handler_priv = priv;
+       struct command **head = command_list_for_parent(cmd_ctx, parent);
+       struct command *nc = command_find(*head, cmd_name);
+       if (NULL == nc) {
+               /* add a new command with help text */
+               struct command_registration cr = {
+                       .name = cmd_name,
+                       .mode = COMMAND_ANY,
+                       .help = help_text,
+                       .usage = usage,
+               };
+               nc = register_command(cmd_ctx, parent, &cr);
+               if (NULL == nc) {
+                       LOG_ERROR("failed to add '%s' help text", cmd_name);
+                       return ERROR_FAIL;
+               }
+               LOG_DEBUG("added '%s' help text", cmd_name);
+               return ERROR_OK;
+       }
+       if (help_text) {
+               bool replaced = false;
+               if (nc->help) {
+                       free((void *)nc->help);
+                       replaced = true;
+               }
+               nc->help = strdup(help_text);
+               if (replaced)
+                       LOG_INFO("replaced existing '%s' help", cmd_name);
+               else
+                       LOG_DEBUG("added '%s' help text", cmd_name);
+       }
+       if (usage) {
+               bool replaced = false;
+               if (nc->usage) {
+                       free((void *)nc->usage);
+                       replaced = true;
+               }
+               nc->usage = strdup(usage);
+               if (replaced)
+                       LOG_INFO("replaced existing '%s' usage", cmd_name);
+               else
+                       LOG_DEBUG("added '%s' usage text", cmd_name);
+       }
+       return ERROR_OK;
 }
 
-command_context_t* copy_command_context(command_context_t* context)
+COMMAND_HANDLER(handle_help_add_command)
 {
-       command_context_t* copy_context = malloc(sizeof(command_context_t));
+       if (CMD_ARGC < 2) {
+               LOG_ERROR("%s: insufficient arguments", CMD_NAME);
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
 
-       *copy_context = *context;
-       
-       return copy_context;
+       /* save help text and remove it from argument list */
+       const char *str = CMD_ARGV[--CMD_ARGC];
+       const char *help = !strcmp(CMD_NAME, "add_help_text") ? str : NULL;
+       const char *usage = !strcmp(CMD_NAME, "add_usage_text") ? str : NULL;
+       if (!help && !usage) {
+               LOG_ERROR("command name '%s' is unknown", CMD_NAME);
+               return ERROR_COMMAND_SYNTAX_ERROR;
+       }
+       /* likewise for the leaf command name */
+       const char *cmd_name = CMD_ARGV[--CMD_ARGC];
+
+       struct command *c = NULL;
+       if (CMD_ARGC > 0) {
+               c = CMD_CTX->commands;
+               int retval = CALL_COMMAND_HANDLER(command_help_find, c, &c);
+               if (ERROR_OK != retval)
+                       return retval;
+       }
+       return help_add_command(CMD_CTX, c, cmd_name, help, usage);
 }
 
-int command_done(command_context_t *context)
+/* sleep command sleeps for <n> milliseconds
+ * this is useful in target startup scripts
+ */
+COMMAND_HANDLER(handle_sleep_command)
 {
-       free(context);
-       context = NULL;
-       
+       bool busy = false;
+       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)
+               return ERROR_COMMAND_SYNTAX_ERROR;
+
+       unsigned long duration = 0;
+       int retval = parse_ulong(CMD_ARGV[0], &duration);
+       if (ERROR_OK != retval)
+               return retval;
+
+       if (!busy) {
+               long long then = timeval_ms();
+               while (timeval_ms() - then < (long long)duration) {
+                       target_call_timer_callbacks_now();
+                       usleep(1000);
+               }
+       } else
+               busy_sleep(duration);
+
        return ERROR_OK;
 }
 
-command_context_t* command_init()
+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.",
+       },
+       {
+               .name = "type",
+               .mode = COMMAND_ANY,
+               .jim_handler = jim_command_type,
+               .usage = "command_name [...]",
+               .help = "Returns the type of built-in command:"
+                       "'native', 'simple', 'group', or 'unknown'. "
+                       "Command can be multiple tokens.",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+static const struct command_registration command_builtin_handlers[] = {
+       {
+               .name = "echo",
+               .handler = jim_echo,
+               .mode = COMMAND_ANY,
+               .help = "Logs a message at \"user\" priority. "
+                       "Output message to stdout. "
+                       "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,
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+struct command_context *command_init(const char *startup_tcl, Jim_Interp *interp)
 {
-       command_context_t* context = malloc(sizeof(command_context_t));
-       
+       struct command_context *context = malloc(sizeof(struct command_context));
+       const char *HostOs;
+
        context->mode = COMMAND_EXEC;
        context->commands = NULL;
        context->current_target = 0;
        context->output_handler = NULL;
        context->output_handler_priv = NULL;
-       
-       register_command(context, NULL, "help", command_print_help,
-                                        COMMAND_EXEC, "display this help");
-       
-       register_command(context, NULL, "sleep", handle_sleep_command,
-                                        COMMAND_ANY, "sleep for <n> milliseconds");
-       
-       register_command(context, NULL, "time", handle_time_command,
-                                        COMMAND_ANY, "time <cmd + args> - execute <cmd + args> and print time it took");
-       
-       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.");
-       
+
+       /* Create a jim interpreter if we were not handed one */
+       if (interp == NULL) {
+               /* Create an interpreter */
+               interp = Jim_CreateInterp();
+               /* Add all the Jim core commands */
+               Jim_RegisterCoreCommands(interp);
+               Jim_InitStaticExtensions(interp);
+       }
+
+       context->interp = interp;
+
+       /* Stick to lowercase for HostOS strings. */
+#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(__APPLE__) || defined(__DARWIN__)
+       HostOs = "darwin";
+#elif defined(__CYGWIN__)
+       HostOs = "cygwin";
+#elif defined(__MINGW32__)
+       HostOs = "mingw32";
+#elif defined(__ECOS)
+       HostOs = "ecos";
+#elif defined(__FreeBSD__)
+       HostOs = "freebsd";
+#elif defined(__NetBSD__)
+       HostOs = "netbsd";
+#elif defined(__OpenBSD__)
+       HostOs = "openbsd";
+#else
+#warning "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, "capture", jim_capture, NULL, NULL);
+
+       register_commands(context, NULL, command_builtin_handlers);
+
+       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_MakeErrorMessage(interp);
+               LOG_USER_N("%s", Jim_GetString(Jim_GetResult(interp), NULL));
+               exit(-1);
+       }
+       Jim_DeleteAssocData(interp, "context");
+
        return context;
 }
 
-/* sleep command sleeps for <n> miliseconds
- * this is useful in target startup scripts
- */
-int handle_sleep_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
+int command_context_mode(struct command_context *cmd_ctx, enum command_mode mode)
 {
-       unsigned long duration = 0;
-       
-       if (argc == 1)
-       {
-               duration = strtoul(args[0], NULL, 0);
-               usleep(duration * 1000);
-       }
+       if (!cmd_ctx)
+               return ERROR_COMMAND_SYNTAX_ERROR;
 
+       cmd_ctx->mode = mode;
        return ERROR_OK;
 }
 
-int handle_fast_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
+void process_jim_events(struct command_context *cmd_ctx)
+{
+       static int recursion;
+       if (recursion)
+               return;
+
+       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) \
+       { \
+               if (!*str) { \
+                       LOG_ERROR("Invalid command argument"); \
+                       return ERROR_COMMAND_ARGUMENT_INVALID; \
+               } \
+               char *end; \
+               *ul = func(str, &end, 0); \
+               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 (min && (min == *ul) && (ERANGE == errno)) { \
+                       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(_ullong, unsigned long long, strtoull, 0, ULLONG_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) \
+       { \
+               functype n; \
+               int retval = parse ## funcname(str, &n); \
+               if (ERROR_OK != retval) \
+                       return retval; \
+               if (n > max) \
+                       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_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)
+
+static int command_parse_bool(const char *in, bool *out,
+       const char *on, const char *off)
 {
-       if (argc!=1)
+       if (strcasecmp(in, on) == 0)
+               *out = true;
+       else if (strcasecmp(in, off) == 0)
+               *out = false;
+       else
                return ERROR_COMMAND_SYNTAX_ERROR;
-       
-       fast_and_dangerous = strcmp("enable", args[0])==0;
-       
        return ERROR_OK;
 }
 
+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;
+}
 
-int handle_time_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
+COMMAND_HELPER(handle_command_parse_bool, bool *out, const char *label)
 {
-       duration_t duration;
-       char *duration_text;
-       int retval;
-       float t;
-       
-       if (argc<1)
-               return ERROR_COMMAND_SYNTAX_ERROR;
-       
-       duration_start_measure(&duration);
-       
-       retval = find_and_run_command(cmd_ctx, cmd_ctx->commands, args, argc);
-       if (retval == ERROR_COMMAND_NOTFOUND)
-       {
-               command_print(cmd_ctx, "Command %s not found", args[0]);
+       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;
+                       }
+                       /* fall through */
+               }
+               case 0:
+                       LOG_INFO("%s is %s", label, *out ? "enabled" : "disabled");
+                       break;
+               default:
+                       return ERROR_COMMAND_SYNTAX_ERROR;
        }
-       
-       duration_stop_measure(&duration, &duration_text);
-       
-       t=duration.duration.tv_sec;
-       t+=((float)duration.duration.tv_usec / 1000000.0);
-       command_print(cmd_ctx, "%s took %fs", args[0], t);
-       
-       free(duration_text);
-
-       return retval;
+       return ERROR_OK;
 }