add jim_handler to command_registration
[openocd.git] / src / helper / command.c
index 119862284d3fa0d3ffbddc06bd30fab7c36d06bf..3cb36ea27590850c4f18131f10d265604b6af7d4 100644 (file)
@@ -2,9 +2,12 @@
  *   Copyright (C) 2005 by Dominic Rath                                    *
  *   Dominic.Rath@gmx.de                                                   *
  *                                                                         *
- *   Copyright (C) 2007,2008 Øyvind Harboe                                      *
+ *   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)                      *
  *                                                                         *
 #include "config.h"
 #endif
 
-#include "replacements.h"
+#if !BUILD_ECOSBOARD
+/* see Embedder-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"
 #include "command.h"
 #include "configuration.h"
-
 #include "log.h"
 #include "time_support.h"
+#include "jim-eventloop.h"
 
-#include <stdlib.h>
-#include <string.h>
-#include <ctype.h>
-#include <stdarg.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <errno.h>
 
-int fast_and_dangerous = 0;
 Jim_Interp *interp = NULL;
 
-int handle_sleep_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);
-
-int run_command(command_context_t *context, command_t *c, char *words[], int num_words);
-
-static void tcl_output(void *privData, const char *file, int line, const char *function, const char *string)
-{              
-       Jim_Obj *tclOutput=(Jim_Obj *)privData;
+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)
+{
+       Jim_Obj *tclOutput = (Jim_Obj *)privData;
        Jim_AppendString(interp, tclOutput, string, strlen(string));
 }
 
-extern command_context_t *global_cmd_ctx;
-
+extern struct command_context *global_cmd_ctx;
 
-static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+void script_debug(Jim_Interp *interp, const char *name,
+               unsigned argc, Jim_Obj *const *argv)
 {
-       /* the private data is stashed in the interp structure */
-       command_t *c;
-       command_context_t *context;
-       int retval;
-       int i;
-       int nwords;
-       char **words;
+       LOG_DEBUG("command - %s", name);
+       for (unsigned i = 0; i < argc; i++)
+       {
+               int len;
+               const char *w = Jim_GetString(argv[i], &len);
 
-       target_call_timer_callbacks_now();
-       LOG_USER_N("%s", ""); /* Keep GDB connection alive*/ 
-       
-       c = interp->cmdPrivData;
-       LOG_DEBUG("script_command - %s", c->name);
+               /* end of line comment? */
+               if (*w == '#')
+                       break;
 
-       words = malloc(sizeof(char *) * argc);
+               LOG_DEBUG("%s - argv[%d]=%s", name, i, w);
+       }
+}
+
+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);
-               if (*w=='#')
-               {
-                       /* hit an end of line comment */
+               const char *w = Jim_GetString(argv[i], &len);
+               /* a comment may end the line early */
+               if (*w == '#')
                        break;
-               }
+
                words[i] = strdup(w);
-               if (words[i] == NULL) 
+               if (words[i] == NULL)
                {
-                       return JIM_ERR;
+                       script_command_args_free(words, i);
+                       return NULL;
                }
-               LOG_DEBUG("script_command - %s, argv[%u]=%s", c->name, i, words[i]);
        }
-       nwords = i;
+       *nwords = i;
+       return words;
+}
 
+static struct command_context *current_command_context(void)
+{
        /* grab the command context from the associated data */
-       context = Jim_GetAssocData(interp, "context");
-       if (context == NULL)
+       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.
                 */
-               context = global_cmd_ctx;
+               cmd_ctx = global_cmd_ctx;
+       }
+       return cmd_ctx;
+}
+
+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;
+       int retval;
+
+       /* 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*/
+
+       script_debug(interp, c->name, argc, argv);
+
+       unsigned nwords;
+       const char **words = script_command_args_alloc(argc, argv, &nwords);
+       if (NULL == words)
+               return JIM_ERR;
+
        /* 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, tclOutput);
-       
-       retval = run_command(context, c, words, nwords);
-       
+
+       struct command_context *cmd_ctx = current_command_context();
+       retval = run_command(cmd_ctx, c, (const char **)words, nwords);
+
        log_remove_callback(tcl_output, tclOutput);
 
        /* We dump output into this local variable */
        Jim_SetResult(interp, tclOutput);
        Jim_DecrRefCount(interp, tclOutput);
 
-       for (i = 0; i < nwords; i++)
-               free(words[i]);
-       free(words);
+       script_command_args_free(words, nwords);
 
        int *return_retval = Jim_GetAssocData(interp, "retval");
        if (return_retval != NULL)
        {
                *return_retval = retval;
        }
-       
-       return (retval==ERROR_OK)?JIM_OK:JIM_ERR;
+
+       return (retval == ERROR_OK)?JIM_OK:JIM_ERR;
 }
 
-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)
+/* nice short description of source file */
+#define __THIS__FILE__ "command.c"
+
+/**
+ * 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 struct command *command_find(struct command *head, const char *name)
 {
-       command_t *c, *p;
-       
-       if (!context || !name)
-               return NULL;
-                               
-       c = malloc(sizeof(command_t));
-       
-       c->name = strdup(name);
+       for (struct command *cc = head; cc; cc = cc->next)
+       {
+               if (strcmp(cc->name, name) == 0)
+                       return cc;
+       }
+       return NULL;
+}
+struct command *command_find_in_context(struct command_context *cmd_ctx,
+               const char *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 struct command *command_new(struct command_context *cmd_ctx,
+               struct command *parent, const struct command_registration *cr)
+{
+       assert(cr->name);
+
+       struct command *c = malloc(sizeof(struct command));
+       memset(c, 0, sizeof(struct command));
+
+       c->name = strdup(cr->name);
+       if (cr->help)
+               c->help = strdup(cr->help);
+       if (cr->usage)
+               c->usage = strdup(cr->usage);
        c->parent = parent;
-       c->children = NULL;
-       c->handler = handler;
-       c->mode = mode;
-       if (!help)
-               help="";
-       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
+       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;
+}
+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(c->name);
+       if (c->help)
+               free((void*)c->help);
+       if (c->usage)
+               free((void*)c->usage);
+       free(c);
+}
+
+static int register_command_handler(struct command *c)
+{
+       int retval = -ENOMEM;
+       const char *full_name = command_name(c, '_');
+       if (NULL == full_name)
+               return retval;
+
+       const char *ocd_name = alloc_printf("ocd_%s", full_name);
+       if (NULL == full_name)
+               goto free_full_name;
+
+       Jim_CreateCommand(interp, ocd_name, script_command, c, NULL);
+       free((void *)ocd_name);
+
+       /* 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);
+       if (NULL == full_name)
+               goto free_full_name;
+
+       Jim_Eval_Named(interp, override_name, __THIS__FILE__, __LINE__);
+       free((void *)override_name);
+
+       retval = ERROR_OK;
+
+free_full_name:
+       free((void *)full_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)
+       {
+               LOG_ERROR("command '%s' is already registered in '%s' context",
+                               name, parent ? parent->name : "<global>");
+               return c;
+       }
+
+       c = command_new(context, parent, cr);
+       if (NULL == c)
+               return NULL;
+
+       if (NULL != c->handler)
+       {
+               int retval = register_command_handler(c);
+               if (ERROR_OK != retval)
                {
-                       parent->children = c;
+                       unregister_command(context, parent, name);
+                       return NULL;
                }
        }
-       else
+
+       if (NULL != cr->jim_handler && NULL == parent)
+               Jim_CreateCommand(interp, cr->name, cr->jim_handler, cr->jim_handler_data, NULL);
+
+       return c;
+}
+
+int register_commands(struct command_context *cmd_ctx, struct command *parent,
+               const struct command_registration *cmds)
+{
+       int retval = ERROR_OK;
+       unsigned i;
+       for (i = 0; cmds[i].name || cmds[i].chain; i++)
        {
-               if (context->commands)
+               const struct command_registration *cr = cmds + i;
+
+               struct command *c = NULL;
+               if (NULL != cr->name)
                {
-                       /* find last command */
-                       for (p = context->commands; p && p->next; p = p->next);
-                       if (p)
-                               p->next = c;
+                       c = register_command(cmd_ctx, parent, cr);
+                       if (NULL == c)
+                       {
+                               retval = ERROR_FAIL;
+                               break;
+                       }
                }
-               else
+               if (NULL != cr->chain)
                {
-                       context->commands = c;
+                       struct command *p = c ? : parent;
+                       retval = register_commands(cmd_ctx, p, cr->chain);
+                       if (ERROR_OK != retval)
+                               break;
                }
        }
-       
-       /* just a placeholder, no handler */
-       if (c->handler==NULL)
-               return c;
-
-       /* If this is a two level command, e.g. "flash banks", then the
-        * "unknown" proc in startup.tcl must redirect to  this command.
-        * 
-        * "flash banks" is translated by "unknown" to "flash_banks"
-        * if such a proc exists
-        */
-       /* Print help for command */
-       const char *t1="";
-       const char *t2="";
-       const char *t3="";
-       /* maximum of two levels :-) */
-       if (c->parent!=NULL)
-       {
-               t1=c->parent->name;
-               t2="_";
-       }
-       t3=c->name;
-       const char *full_name=alloc_printf("ocd_%s%s%s", t1, t2, t3);
-       Jim_CreateCommand(interp, full_name, script_command, c, NULL);
-       free((void *)full_name);
-       
-       /* we now need to add an overrideable proc */
-       const char *override_name=alloc_printf("proc %s%s%s {args} {eval \"ocd_%s%s%s $args\";return \"\"}", t1, t2, t3, t1, t2, t3);
-       Jim_Eval(interp, override_name);        
-       free((void *)override_name);
-       
-       /* 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_Obj *cmd_entry=Jim_NewListObj(interp, NULL, 0);
-       
-       Jim_Obj *cmd_list=Jim_NewListObj(interp, NULL, 0);
-
-       /* maximum of two levels :-) */
-       if (c->parent!=NULL)
-       {
-               Jim_ListAppendElement(interp, cmd_list, Jim_NewStringObj(interp, c->parent->name, -1));
-       } 
-       Jim_ListAppendElement(interp, cmd_list, Jim_NewStringObj(interp, c->name, -1));
-       
-       Jim_ListAppendElement(interp, cmd_entry, cmd_list);
-       Jim_ListAppendElement(interp, cmd_entry, Jim_NewStringObj(interp, help, -1));
-       Jim_ListAppendElement(interp, helptext, cmd_entry);
-       return c;
+       if (ERROR_OK != retval)
+       {
+               for (unsigned j = 0; j < i; j++)
+                       unregister_command(cmd_ctx, parent, cmds[j].name);
+       }
+       return retval;
 }
 
-int unregister_all_commands(command_context_t *context)
+int unregister_all_commands(struct command_context *context,
+               struct command *parent)
 {
-       command_t *c, *c2;
-       
        if (context == NULL)
                return ERROR_OK;
-       
-       while(NULL != context->commands)
+
+       struct command **head = command_list_for_parent(context, parent);
+       while (NULL != *head)
        {
-               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;
-               }
-               
-               context->commands = context->commands->next;
-               
-               free(c->name);
-               c->name = NULL;
-               free(c);
-               c = NULL;               
+               struct command *tmp = *head;
+               *head = tmp->next;
+               command_free(tmp);
        }
-       
+
        return ERROR_OK;
 }
 
-int unregister_command(command_context_t *context, char *name)
+int unregister_command(struct command_context *context,
+               struct command *parent, const char *name)
 {
-       command_t *c, *p = NULL, *c2;
-       
        if ((!context) || (!name))
                return ERROR_INVALID_ARGUMENTS;
-       
-       /* find command */
-       for (c = context->commands; c; c = c->next)
+
+       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)
-               {
-                       /* 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);
-                                       free(c2);
-                               }
-                       }
-                       
-                       /* delete command */
-                       free(c->name);
-                       free(c);
-               }
-               
-               /* remember the last command for unlinking */
-               p = c;
+               if (strcmp(name, c->name) != 0)
+                       continue;
+
+               if (p)
+                       p->next = c->next;
+               else
+                       *head = c->next;
+
+               command_free(c);
+               return ERROR_OK;
        }
-       
+
        return ERROR_OK;
 }
 
-void command_output_text(command_context_t *context, const char *data)
+void command_output_text(struct command_context *context, const char *data)
 {
-       if( context && context->output_handler && data  ){
-               context->output_handler( context, data );
+       if (context && context->output_handler && data) {
+               context->output_handler(context, data);
        }
 }
 
-void command_print_n(command_context_t *context, char *format, ...)
+void command_print_sameline(struct command_context *context, const char *format, ...)
 {
        char *string;
-       
+
        va_list ap;
        va_start(ap, format);
 
@@ -328,19 +444,19 @@ void command_print_n(command_context_t *context, char *format, ...)
        {
                /* 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);
+               /* 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;
 
@@ -353,43 +469,66 @@ void command_print(command_context_t *context, char *format, ...)
                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);
+               /* We already printed it above */
+               /* command_output_text(context, string); */
                free(string);
        }
 
        va_end(ap);
 }
 
-int run_command(command_context_t *context, command_t *c, char *words[], int num_words)
+static char *__command_name(struct command *c, char delim, unsigned extra)
 {
-       int start_word=0;
-       if (!((context->mode == COMMAND_CONFIG) || (c->mode == COMMAND_ANY) || (c->mode == context->mode) ))
+       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);
+       }
+       return name;
+}
+char *command_name(struct command *c, char delim)
+{
+       return __command_name(c, delim, 0);
+}
+
+static int run_command(struct command_context *context,
+               struct command *c, const char *words[], unsigned num_words)
+{
+       if (!((context->mode == COMMAND_CONFIG) || (c->mode == COMMAND_ANY) || (c->mode == context->mode)))
        {
                /* Config commands can not run after the config stage */
-               LOG_ERROR("Illegal mode for command");
+               LOG_ERROR("Command '%s' only runs during configuration stage", c->name);
                return ERROR_FAIL;
        }
-       
-       int retval = c->handler(context, c->name, words + start_word + 1, num_words - start_word - 1);
+
+       struct command_invocation cmd = {
+                       .ctx = context,
+                       .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 */
-               const char *t1="";
-               const char *t2="";
-               const char *t3="";
-               /* maximum of two levels :-) */
-               if (c->parent!=NULL)
-               {
-                       t1=c->parent->name;
-                       t2=" ";
-               }
-               t3=c->name;
-               command_run_linef(context, "help {%s%s%s}", t1, t2, t3);
+               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)
        {
@@ -400,20 +539,20 @@ int run_command(command_context_t *context, command_t *c, char *words[], int num
                /* 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(command_context_t *context, char *line)
+int command_run_line(struct command_context *context, char *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 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
@@ -429,22 +568,22 @@ int command_run_line(command_context_t *context, char *line)
                retcode = Jim_SetAssocData(interp, "retval", NULL, &retval);
                if (retcode == JIM_OK)
                {
-                       retcode = Jim_Eval(interp, line);
-                       
+                       retcode = Jim_Eval_Named(interp, line, __THIS__FILE__, __LINE__);
+
                        Jim_DeleteAssocData(interp, "retval");
-               }       
+               }
                Jim_DeleteAssocData(interp, "context");
        }
        if (retcode == JIM_ERR) {
-               if (retval!=ERROR_COMMAND_CLOSE_CONNECTION)
+               if (retval != ERROR_COMMAND_CLOSE_CONNECTION)
                {
                        /* We do not print the connection closed error message */
                        Jim_PrintErrorMessage(interp);
                }
-               if (retval==ERROR_OK)
+               if (retval == ERROR_OK)
                {
                        /* It wasn't a low level OpenOCD command that failed */
-                       return ERROR_FAIL; 
+                       return ERROR_FAIL;
                }
                return retval;
        } else if (retcode == JIM_EXIT) {
@@ -455,60 +594,63 @@ int command_run_line(command_context_t *context, char *line)
                int reslen;
 
                result = Jim_GetString(Jim_GetResult(interp), &reslen);
-               if (reslen) {
+               if (reslen > 0)
+               {
                        int i;
-                       char buff[256+1];
+                       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; 
+                               strncpy(buff, result + i, chunk);
+                               buff[chunk] = 0;
                                LOG_USER_N("%s", buff);
                        }
                        LOG_USER_N("%s", "\n");
                }
+               retval = ERROR_OK;
        }
        return retval;
 }
 
-int command_run_linef(command_context_t *context, char *format, ...)
+int command_run_linef(struct command_context *context, const char *format, ...)
 {
-       int retval=ERROR_FAIL;
+       int retval = ERROR_FAIL;
        char *string;
        va_list ap;
        va_start(ap, format);
        string = alloc_vprintf(format, ap);
-       if (string!=NULL)
+       if (string != NULL)
        {
-               retval=command_run_line(context, string);
+               retval = command_run_line(context, string);
        }
        va_end(ap);
        return retval;
 }
 
-void command_set_output_handler(command_context_t* context, int (*output_handler)(struct command_context_s *context, const char* line), 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;
 }
 
-command_context_t* copy_command_context(command_context_t* context)
+struct command_context* copy_command_context(struct command_context* context)
 {
-       command_context_t* copy_context = malloc(sizeof(command_context_t));
+       struct command_context* copy_context = malloc(sizeof(struct command_context));
 
        *copy_context = *context;
 
        return copy_context;
 }
 
-int command_done(command_context_t *context)
+int command_done(struct command_context *context)
 {
        free(context);
        context = NULL;
-       
+
        return ERROR_OK;
 }
 
@@ -523,7 +665,7 @@ static int jim_find(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
                return JIM_ERR;
        Jim_Obj *result = Jim_NewStringObj(interp, full_path, strlen(full_path));
        free(full_path);
-       
+
        Jim_SetResult(interp, result);
        return JIM_OK;
 }
@@ -560,7 +702,7 @@ static size_t openocd_jim_fwrite(const void *_ptr, size_t size, size_t n, void *
        }
        /* GRR we must chunk - not null terminated */
        while (nbytes) {
-               char chunk[128+1];
+               char chunk[128 + 1];
                int x;
 
                x = nbytes;
@@ -576,7 +718,7 @@ static size_t openocd_jim_fwrite(const void *_ptr, size_t size, size_t n, void *
                ptr += x;
                nbytes -= x;
        }
-       
+
        return n;
 }
 
@@ -620,10 +762,311 @@ static char* openocd_jim_fgets(char *s, int size, void *cookie)
        return NULL;
 }
 
-command_context_t* command_init()
+static int jim_capture(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);
+
+       /* 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, tclOutput);
+
+       retcode = Jim_Eval_Named(interp, str, __THIS__FILE__, __LINE__);
+
+       log_remove_callback(tcl_output, tclOutput);
+
+       /* We dump output into this local variable */
+       Jim_SetResult(interp, tclOutput);
+       Jim_DecrRefCount(interp, tclOutput);
+
+       return retcode;
+}
+
+static COMMAND_HELPER(command_help_find, struct command *head,
+               struct command **out)
+{
+       if (0 == CMD_ARGC)
+               return ERROR_INVALID_ARGUMENTS;
+       *out = command_find(head, CMD_ARGV[0]);
+       if (NULL == *out)
+               return ERROR_INVALID_ARGUMENTS;
+       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);
+
+static COMMAND_HELPER(command_help_show_list, struct command *head, unsigned n,
+               bool show_help)
+{
+       for (struct command *c = head; NULL != c; c = c->next)
+               CALL_COMMAND_HANDLER(command_help_show, c, n, show_help);
+       return ERROR_OK;
+}
+static COMMAND_HELPER(command_help_show, struct command *c, unsigned n,
+               bool show_help)
+{
+       const char *usage = c->usage ? : "";
+       const char *help = "";
+       const char *sep = "";
+       if (show_help && c->help)
+       {
+               help = c->help ? : "";
+               sep = c->usage ? " | " : "";
+       }
+       command_run_linef(CMD_CTX, "cmd_help {%s} {%s%s%s} %d",
+                       command_name(c, ' '), usage, sep, help, n);
+
+       if (++n >= 2)
+               return ERROR_OK;
+
+       return CALL_COMMAND_HANDLER(command_help_show_list,
+                       c->children, n, show_help);
+}
+COMMAND_HANDLER(handle_help_command)
+{
+       struct command *c = CMD_CTX->commands;
+
+       if (0 == CMD_ARGC)
+               return CALL_COMMAND_HANDLER(command_help_show_list, c, 0, true);
+
+       int retval = CALL_COMMAND_HANDLER(command_help_find, c, &c);
+       if (ERROR_OK != retval)
+               return retval;
+
+       return CALL_COMMAND_HANDLER(command_help_show, c, 0, true);
+}
+
+COMMAND_HANDLER(handle_usage_command)
 {
-       command_context_t* context = malloc(sizeof(command_context_t));
-       extern unsigned const char startup_tcl[];
+       struct command *c = CMD_CTX->commands;
+
+       if (0 == CMD_ARGC)
+               return CALL_COMMAND_HANDLER(command_help_show_list, c, 0, false);
+
+       int retval = CALL_COMMAND_HANDLER(command_help_find, c, &c);
+       if (ERROR_OK != retval)
+               return retval;
+
+       return CALL_COMMAND_HANDLER(command_help_show, c, 0, false);
+}
+
+static int command_unknown_find(unsigned argc, Jim_Obj *const *argv,
+               struct command *head, struct command **out)
+{
+       if (0 == argc)
+               return argc;
+       struct command *c = command_find(head, Jim_GetString(argv[0], NULL));
+       if (NULL == c)
+               return argc;
+       *out = c;
+       return command_unknown_find(--argc, ++argv, (*out)->children, out);
+}
+
+static int command_unknown(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
+{
+       const char *cmd_name = Jim_GetString(argv[0], NULL);
+       script_debug(interp, cmd_name, argc - 1, argv + 1);
+
+       struct command_context *cmd_ctx = current_command_context();
+       struct command *c = cmd_ctx->commands;
+       int remaining = command_unknown_find(argc - 1, argv + 1, c, &c);
+       // if nothing could be consumed, then it's really an unknown command
+       if (remaining == argc - 1)
+       {
+               const char *cmd = Jim_GetString(argv[1], 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, "help");
+               if (NULL == c)
+               {
+                       LOG_ERROR("unknown command, but help is missing too");
+                       return JIM_ERR;
+               }
+               count = argc - remaining;
+               start = argv;
+               found = false;
+       }
+       // 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);
+       }
+
+       unsigned nwords;
+       const char **words = script_command_args_alloc(count, start, &nwords);
+       if (NULL == words)
+               return JIM_ERR;
+
+       int retval = run_command(cmd_ctx, c, words, nwords);
+
+       script_command_args_free(words, nwords);
+
+       if (!found && ERROR_OK == retval)
+               retval = ERROR_FAIL;
+
+       return retval;
+}
+
+int help_add_command(struct command_context *cmd_ctx, struct command *parent,
+               const char *cmd_name, const char *help_text, const char *usage)
+{
+       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);
+       }
+       else
+       {
+               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);
+       }
+       return ERROR_OK;
+}
+
+COMMAND_HANDLER(handle_help_add_command)
+{
+       if (CMD_ARGC < 2)
+       {
+               LOG_ERROR("%s: insufficient arguments", CMD_NAME);
+               return ERROR_INVALID_ARGUMENTS;
+       }
+
+       // save help text and remove it from argument list
+       const char *help_text = CMD_ARGV[--CMD_ARGC];
+       // 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_text, NULL);
+}
+
+/* sleep command sleeps for <n> miliseconds
+ * this is useful in target startup scripts
+ */
+COMMAND_HANDLER(handle_sleep_command)
+{
+       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;
+}
+
+static const struct command_registration command_builtin_handlers[] = {
+       {
+               .name = "add_help_text",
+               .handler = &handle_help_add_command,
+               .mode = COMMAND_ANY,
+               .help = "add new command help text",
+               .usage = "<command> [...] <help_text>]",
+       },
+       {
+               .name = "sleep",
+               .handler = &handle_sleep_command,
+               .mode = COMMAND_ANY,
+               .help = "sleep for n milliseconds.  "
+                       "\"busy\" will busy wait",
+               .usage = "<n> [busy]",
+       },
+       {
+               .name = "help",
+               .handler = &handle_help_command,
+               .mode = COMMAND_ANY,
+               .help = "show built-in command help",
+               .usage = "[<command_name> ...]",
+       },
+       {
+               .name = "usage",
+               .handler = &handle_usage_command,
+               .mode = COMMAND_ANY,
+               .help = "show command usage",
+               .usage = "[<command_name> ...]",
+       },
+       COMMAND_REGISTRATION_DONE
+};
+
+struct command_context* command_init(const char *startup_tcl)
+{
+       struct command_context* context = malloc(sizeof(struct command_context));
+       const char *HostOs;
 
        context->mode = COMMAND_EXEC;
        context->commands = NULL;
@@ -631,7 +1074,7 @@ command_context_t* command_init()
        context->output_handler = NULL;
        context->output_handler_priv = NULL;
 
-#ifdef JIM_EMBEDDED
+#if !BUILD_ECOSBOARD
        Jim_InitEmbedded();
        /* Create an interpreter */
        interp = Jim_CreateInterp();
@@ -639,8 +1082,36 @@ command_context_t* command_init()
        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, "unknown", &command_unknown, NULL, NULL);
        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;
@@ -651,26 +1122,25 @@ command_context_t* command_init()
        interp->cb_vfprintf = openocd_jim_vfprintf;
        interp->cb_fflush = openocd_jim_fflush;
        interp->cb_fgets = openocd_jim_fgets;
-       
-       add_default_dirs();
 
-       if (Jim_Eval(interp, startup_tcl)==JIM_ERR)
+       register_commands(context, NULL, command_builtin_handlers);
+
+#if !BUILD_ECOSBOARD
+       Jim_EventLoopOnLoad(interp);
+#endif
+       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 compile time)");
+               LOG_ERROR("Failed to run startup.tcl (embedded into OpenOCD)");
                Jim_PrintErrorMessage(interp);
                exit(-1);
        }
+       Jim_DeleteAssocData(interp, "context");
 
-       register_command(context, NULL, "sleep", handle_sleep_command,
-                                        COMMAND_ANY, "sleep for <n> milliseconds");
-       
-       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.");
-       
        return context;
 }
 
-int command_context_mode(command_context_t *cmd_ctx, enum command_mode mode)
+int command_context_mode(struct command_context *cmd_ctx, enum command_mode mode)
 {
        if (!cmd_ctx)
                return ERROR_INVALID_ARGUMENTS;
@@ -679,48 +1149,125 @@ int command_context_mode(command_context_t *cmd_ctx, enum command_mode mode)
        return ERROR_OK;
 }
 
-/* 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)
+void process_jim_events(void)
 {
-       unsigned long duration = 0;
-       
-       if (argc == 1)
+#if !BUILD_ECOSBOARD
+       static int recursion = 0;
+
+       if (!recursion)
        {
-               duration = strtoul(args[0], NULL, 0);
-               usleep(duration * 1000);
+               recursion++;
+               Jim_ProcessEvents (interp, JIM_ALL_EVENTS | JIM_DONT_WAIT);
+               recursion--;
        }
-
-       return ERROR_OK;
+#endif
 }
 
-int handle_fast_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
+#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;
+       return  ERROR_OK;
 }
 
-void register_jim(struct command_context_s *cmd_ctx, const char *name, int (*cmd)(Jim_Interp *interp, int argc, Jim_Obj *const *argv), const char *help)
-{
-       Jim_CreateCommand(interp, name, cmd, NULL, NULL);
-
-       /* FIX!!! it would be prettier to invoke add_help_text... 
-          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_Obj *cmd_entry=Jim_NewListObj(interp, NULL, 0);
-       
-       Jim_Obj *cmd_list=Jim_NewListObj(interp, NULL, 0);
-       Jim_ListAppendElement(interp, cmd_list, Jim_NewStringObj(interp, name, -1));
-       
-       Jim_ListAppendElement(interp, cmd_entry, cmd_list);
-       Jim_ListAppendElement(interp, cmd_entry, Jim_NewStringObj(interp, help, -1));
-       Jim_ListAppendElement(interp, helptext, cmd_entry);
+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_INVALID_ARGUMENTS;
+}
+
+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_INVALID_ARGUMENTS;
+               }
+               // fall through
+       }
+       case 0:
+               LOG_INFO("%s is %s", label, *out ? "enabled" : "disabled");
+               break;
+       default:
+               return ERROR_INVALID_ARGUMENTS;
+       }
+       return ERROR_OK;
 }

Linking to existing account procedure

If you already have an account and want to add another login method you MUST first sign in with your existing account and then change URL to read https://review.openocd.org/login/?link to get to this page again but this time it'll work for linking. Thank you.

SSH host keys fingerprints

1024 SHA256:YKx8b7u5ZWdcbp7/4AeXNaqElP49m6QrwfXaqQGJAOk gerrit-code-review@openocd.zylin.com (DSA)
384 SHA256:jHIbSQa4REvwCFG4cq5LBlBLxmxSqelQPem/EXIrxjk gerrit-code-review@openocd.org (ECDSA)
521 SHA256:UAOPYkU9Fjtcao0Ul/Rrlnj/OsQvt+pgdYSZ4jOYdgs gerrit-code-review@openocd.org (ECDSA)
256 SHA256:A13M5QlnozFOvTllybRZH6vm7iSt0XLxbA48yfc2yfY gerrit-code-review@openocd.org (ECDSA)
256 SHA256:spYMBqEYoAOtK7yZBrcwE8ZpYt6b68Cfh9yEVetvbXg gerrit-code-review@openocd.org (ED25519)
+--[ED25519 256]--+
|=..              |
|+o..   .         |
|*.o   . .        |
|+B . . .         |
|Bo. = o S        |
|Oo.+ + =         |
|oB=.* = . o      |
| =+=.+   + E     |
|. .=o   . o      |
+----[SHA256]-----+
2048 SHA256:0Onrb7/PHjpo6iVZ7xQX2riKN83FJ3KGU0TvI0TaFG4 gerrit-code-review@openocd.zylin.com (RSA)