1 /***************************************************************************
2 * Copyright (C) 2005 by Dominic Rath *
3 * Dominic.Rath@gmx.de *
5 * Copyright (C) 2007,2008 Øyvind Harboe *
6 * oyvind.harboe@zylin.com *
8 * Copyright (C) 2008, Duane Ellis *
9 * openocd@duaneeellis.com *
11 * part of this file is taken from libcli (libcli.sourceforge.net) *
12 * Copyright (C) David Parrish (david@dparrish.com) *
14 * This program is free software; you can redistribute it and/or modify *
15 * it under the terms of the GNU General Public License as published by *
16 * the Free Software Foundation; either version 2 of the License, or *
17 * (at your option) any later version. *
19 * This program is distributed in the hope that it will be useful, *
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
22 * GNU General Public License for more details. *
24 * You should have received a copy of the GNU General Public License *
25 * along with this program; if not, write to the *
26 * Free Software Foundation, Inc., *
27 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
28 ***************************************************************************/
34 /* see Embedder-HOWTO.txt in Jim Tcl project hosted on BerliOS*/
38 // @todo the inclusion of target.h here is a layering violation
41 #include "configuration.h"
43 #include "time_support.h"
44 #include "jim-eventloop.h"
47 int fast_and_dangerous
= 0;
48 Jim_Interp
*interp
= NULL
;
50 static int run_command(command_context_t
*context
,
51 command_t
*c
, const char *words
[], unsigned num_words
);
53 static void tcl_output(void *privData
, const char *file
, unsigned line
,
54 const char *function
, const char *string
)
56 Jim_Obj
*tclOutput
= (Jim_Obj
*)privData
;
57 Jim_AppendString(interp
, tclOutput
, string
, strlen(string
));
60 extern command_context_t
*global_cmd_ctx
;
62 void script_debug(Jim_Interp
*interp
, const char *name
,
63 unsigned argc
, Jim_Obj
*const *argv
)
65 LOG_DEBUG("command - %s", name
);
66 for (unsigned i
= 0; i
< argc
; i
++)
69 const char *w
= Jim_GetString(argv
[i
], &len
);
71 /* end of line comment? */
75 LOG_DEBUG("%s - argv[%d]=%s", name
, i
, w
);
79 static int script_command(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
81 /* the private data is stashed in the interp structure */
83 command_context_t
*context
;
89 /* DANGER!!!! be careful what we invoke here, since interp->cmdPrivData might
90 * get overwritten by running other Jim commands! Treat it as an
91 * emphemeral global variable that is used in lieu of an argument
92 * to the fn and fish it out manually.
94 c
= interp
->cmdPrivData
;
97 LOG_ERROR("BUG: interp->cmdPrivData == NULL");
100 target_call_timer_callbacks_now();
101 LOG_USER_N("%s", ""); /* Keep GDB connection alive*/
103 script_debug(interp
, c
->name
, argc
, argv
);
105 words
= malloc(sizeof(char *) * (argc
+ 1));
107 for (i
= 0; i
< argc
; i
++)
110 const char *w
= Jim_GetString(argv
[i
], &len
);
113 /* hit an end of line comment */
116 words
[i
+ 1] = strdup(w
);
117 if (words
[i
+ 1] == NULL
)
120 for (j
= 0; j
< i
; j
++)
128 /* grab the command context from the associated data */
129 context
= Jim_GetAssocData(interp
, "context");
132 /* Tcl can invoke commands directly instead of via command_run_line(). This would
133 * happen when the Jim Tcl interpreter is provided by eCos.
135 context
= global_cmd_ctx
;
138 /* capture log output and return it */
139 Jim_Obj
*tclOutput
= Jim_NewStringObj(interp
, "", 0);
140 /* a garbage collect can happen, so we need a reference count to this object */
141 Jim_IncrRefCount(tclOutput
);
143 log_add_callback(tcl_output
, tclOutput
);
145 // turn words[0] into args[-1] with this cast
146 retval
= run_command(context
, c
, (const char **)words
+ 1, nwords
);
148 log_remove_callback(tcl_output
, tclOutput
);
150 /* We dump output into this local variable */
151 Jim_SetResult(interp
, tclOutput
);
152 Jim_DecrRefCount(interp
, tclOutput
);
154 for (i
= 0; i
< nwords
; i
++)
158 int *return_retval
= Jim_GetAssocData(interp
, "retval");
159 if (return_retval
!= NULL
)
161 *return_retval
= retval
;
164 return (retval
== ERROR_OK
)?JIM_OK
:JIM_ERR
;
167 static Jim_Obj
*command_name_list(struct command_s
*c
)
169 Jim_Obj
*cmd_list
= c
->parent
?
170 command_name_list(c
->parent
) :
171 Jim_NewListObj(interp
, NULL
, 0);
172 Jim_ListAppendElement(interp
, cmd_list
,
173 Jim_NewStringObj(interp
, c
->name
, -1));
178 static void command_helptext_add(Jim_Obj
*cmd_list
, const char *help
)
180 Jim_Obj
*cmd_entry
= Jim_NewListObj(interp
, NULL
, 0);
181 Jim_ListAppendElement(interp
, cmd_entry
, cmd_list
);
182 Jim_ListAppendElement(interp
, cmd_entry
,
183 Jim_NewStringObj(interp
, help
? : "", -1));
185 /* accumulate help text in Tcl helptext list. */
186 Jim_Obj
*helptext
= Jim_GetGlobalVariableStr(interp
,
187 "ocd_helptext", JIM_ERRMSG
);
188 if (Jim_IsShared(helptext
))
189 helptext
= Jim_DuplicateObj(interp
, helptext
);
190 Jim_ListAppendElement(interp
, helptext
, cmd_entry
);
193 /* nice short description of source file */
194 #define __THIS__FILE__ "command.c"
197 * Find a command by name from a list of commands.
198 * @returns The named command if found, or NULL.
200 static struct command_s
*command_find(struct command_s
**head
, const char *name
)
203 for (struct command_s
*cc
= *head
; cc
; cc
= cc
->next
)
205 if (strcmp(cc
->name
, name
) == 0)
212 * Add the command to the end of linked list.
213 * @returns Returns false if the named command already exists in the list.
214 * Returns true otherwise.
216 static void command_add_child(struct command_s
**head
, struct command_s
*c
)
224 struct command_s
*cc
= *head
;
225 while (cc
->next
) cc
= cc
->next
;
229 command_t
* register_command(command_context_t
*context
,
230 command_t
*parent
, char *name
, command_handler_t handler
,
231 enum command_mode mode
, char *help
)
233 if (!context
|| !name
)
236 struct command_s
**head
= parent
? &parent
->children
: &context
->commands
;
237 struct command_s
*c
= command_find(head
, name
);
241 c
= malloc(sizeof(command_t
));
243 c
->name
= strdup(name
);
246 c
->handler
= handler
;
250 command_add_child(head
, c
);
252 command_helptext_add(command_name_list(c
), help
);
254 /* just a placeholder, no handler */
255 if (c
->handler
== NULL
)
258 const char *full_name
= command_name(c
, '_');
260 const char *ocd_name
= alloc_printf("ocd_%s", full_name
);
261 Jim_CreateCommand(interp
, ocd_name
, script_command
, c
, NULL
);
262 free((void *)ocd_name
);
264 /* we now need to add an overrideable proc */
265 const char *override_name
= alloc_printf("proc %s {args} {"
266 "if {[catch {eval ocd_%s $args}] == 0} "
267 "{return \"\"} else {return -code error}}",
268 full_name
, full_name
);
269 Jim_Eval_Named(interp
, override_name
, __THIS__FILE__
, __LINE__
);
270 free((void *)override_name
);
272 free((void *)full_name
);
277 int unregister_all_commands(command_context_t
*context
)
284 while (NULL
!= context
->commands
)
286 c
= context
->commands
;
288 while (NULL
!= c
->children
)
291 c
->children
= c
->children
->next
;
298 context
->commands
= context
->commands
->next
;
309 int unregister_command(command_context_t
*context
, char *name
)
311 command_t
*c
, *p
= NULL
, *c2
;
313 if ((!context
) || (!name
))
314 return ERROR_INVALID_ARGUMENTS
;
317 c
= context
->commands
;
321 if (strcmp(name
, c
->name
) == 0)
330 /* first element in command list */
331 context
->commands
= c
->next
;
334 /* unregister children */
335 while (NULL
!= c
->children
)
338 c
->children
= c
->children
->next
;
353 /* remember the last command for unlinking */
361 void command_output_text(command_context_t
*context
, const char *data
)
363 if (context
&& context
->output_handler
&& data
) {
364 context
->output_handler(context
, data
);
368 void command_print_sameline(command_context_t
*context
, const char *format
, ...)
373 va_start(ap
, format
);
375 string
= alloc_vprintf(format
, ap
);
378 /* we want this collected in the log + we also want to pick it up as a tcl return
381 * The latter bit isn't precisely neat, but will do for now.
383 LOG_USER_N("%s", string
);
384 /* We already printed it above */
385 /* command_output_text(context, string); */
392 void command_print(command_context_t
*context
, const char *format
, ...)
397 va_start(ap
, format
);
399 string
= alloc_vprintf(format
, ap
);
402 strcat(string
, "\n"); /* alloc_vprintf guaranteed the buffer to be at least one char longer */
403 /* we want this collected in the log + we also want to pick it up as a tcl return
406 * The latter bit isn't precisely neat, but will do for now.
408 LOG_USER_N("%s", string
);
409 /* We already printed it above */
410 /* command_output_text(context, string); */
417 static char *__command_name(struct command_s
*c
, char delim
, unsigned extra
)
420 unsigned len
= strlen(c
->name
);
421 if (NULL
== c
->parent
) {
422 // allocate enough for the name, child names, and '\0'
423 name
= malloc(len
+ extra
+ 1);
424 strcpy(name
, c
->name
);
426 // parent's extra must include both the space and name
427 name
= __command_name(c
->parent
, delim
, 1 + len
+ extra
);
428 char dstr
[2] = { delim
, 0 };
430 strcat(name
, c
->name
);
434 char *command_name(struct command_s
*c
, char delim
)
436 return __command_name(c
, delim
, 0);
439 static int run_command(command_context_t
*context
,
440 command_t
*c
, const char *words
[], unsigned num_words
)
443 if (!((context
->mode
== COMMAND_CONFIG
) || (c
->mode
== COMMAND_ANY
) || (c
->mode
== context
->mode
)))
445 /* Config commands can not run after the config stage */
446 LOG_ERROR("Command '%s' only runs during configuration stage", c
->name
);
450 unsigned argc
= num_words
- start_word
- 1;
451 const char **args
= words
+ start_word
+ 1;
452 int retval
= c
->handler(context
, args
, argc
);
453 if (retval
== ERROR_COMMAND_SYNTAX_ERROR
)
455 /* Print help for command */
456 char *full_name
= command_name(c
, ' ');
457 if (NULL
!= full_name
) {
458 command_run_linef(context
, "help %s", full_name
);
463 else if (retval
== ERROR_COMMAND_CLOSE_CONNECTION
)
465 /* just fall through for a shutdown request */
467 else if (retval
!= ERROR_OK
)
469 /* we do not print out an error message because the command *should*
470 * have printed out an error
472 LOG_DEBUG("Command failed with error code %d", retval
);
478 int command_run_line(command_context_t
*context
, char *line
)
480 /* all the parent commands have been registered with the interpreter
481 * so, can just evaluate the line as a script and check for
484 /* run the line thru a script engine */
485 int retval
= ERROR_FAIL
;
487 /* Beware! This code needs to be reentrant. It is also possible
488 * for OpenOCD commands to be invoked directly from Tcl. This would
489 * happen when the Jim Tcl interpreter is provided by eCos for
492 Jim_DeleteAssocData(interp
, "context");
493 retcode
= Jim_SetAssocData(interp
, "context", NULL
, context
);
494 if (retcode
== JIM_OK
)
496 /* associated the return value */
497 Jim_DeleteAssocData(interp
, "retval");
498 retcode
= Jim_SetAssocData(interp
, "retval", NULL
, &retval
);
499 if (retcode
== JIM_OK
)
501 retcode
= Jim_Eval_Named(interp
, line
, __THIS__FILE__
, __LINE__
);
503 Jim_DeleteAssocData(interp
, "retval");
505 Jim_DeleteAssocData(interp
, "context");
507 if (retcode
== JIM_ERR
) {
508 if (retval
!= ERROR_COMMAND_CLOSE_CONNECTION
)
510 /* We do not print the connection closed error message */
511 Jim_PrintErrorMessage(interp
);
513 if (retval
== ERROR_OK
)
515 /* It wasn't a low level OpenOCD command that failed */
519 } else if (retcode
== JIM_EXIT
) {
521 /* exit(Jim_GetExitCode(interp)); */
526 result
= Jim_GetString(Jim_GetResult(interp
), &reslen
);
531 for (i
= 0; i
< reslen
; i
+= 256)
537 strncpy(buff
, result
+ i
, chunk
);
539 LOG_USER_N("%s", buff
);
541 LOG_USER_N("%s", "\n");
548 int command_run_linef(command_context_t
*context
, const char *format
, ...)
550 int retval
= ERROR_FAIL
;
553 va_start(ap
, format
);
554 string
= alloc_vprintf(format
, ap
);
557 retval
= command_run_line(context
, string
);
563 void command_set_output_handler(command_context_t
* context
,
564 command_output_handler_t output_handler
, void *priv
)
566 context
->output_handler
= output_handler
;
567 context
->output_handler_priv
= priv
;
570 command_context_t
* copy_command_context(command_context_t
* context
)
572 command_context_t
* copy_context
= malloc(sizeof(command_context_t
));
574 *copy_context
= *context
;
579 int command_done(command_context_t
*context
)
587 /* find full path to file */
588 static int jim_find(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
592 const char *file
= Jim_GetString(argv
[1], NULL
);
593 char *full_path
= find_file(file
);
594 if (full_path
== NULL
)
596 Jim_Obj
*result
= Jim_NewStringObj(interp
, full_path
, strlen(full_path
));
599 Jim_SetResult(interp
, result
);
603 static int jim_echo(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
607 const char *str
= Jim_GetString(argv
[1], NULL
);
612 static size_t openocd_jim_fwrite(const void *_ptr
, size_t size
, size_t n
, void *cookie
)
618 /* make it a char easier to read code */
622 if (ptr
== NULL
|| interp
== NULL
|| nbytes
== 0) {
626 /* do we have to chunk it? */
627 if (ptr
[nbytes
] == 0)
629 /* no it is a C style string */
630 LOG_USER_N("%s", ptr
);
633 /* GRR we must chunk - not null terminated */
643 memcpy(chunk
, ptr
, x
);
647 LOG_USER_N("%s", chunk
);
655 static size_t openocd_jim_fread(void *ptr
, size_t size
, size_t n
, void *cookie
)
657 /* TCL wants to read... tell him no */
661 static int openocd_jim_vfprintf(void *cookie
, const char *fmt
, va_list ap
)
672 cp
= alloc_vprintf(fmt
, ap
);
675 LOG_USER_N("%s", cp
);
682 static int openocd_jim_fflush(void *cookie
)
684 /* nothing to flush */
688 static char* openocd_jim_fgets(char *s
, int size
, void *cookie
)
695 static int jim_capture(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
700 const char *str
= Jim_GetString(argv
[1], NULL
);
702 /* capture log output and return it */
703 Jim_Obj
*tclOutput
= Jim_NewStringObj(interp
, "", 0);
704 /* a garbage collect can happen, so we need a reference count to this object */
705 Jim_IncrRefCount(tclOutput
);
707 log_add_callback(tcl_output
, tclOutput
);
709 retcode
= Jim_Eval_Named(interp
, str
, __THIS__FILE__
, __LINE__
);
711 log_remove_callback(tcl_output
, tclOutput
);
713 /* We dump output into this local variable */
714 Jim_SetResult(interp
, tclOutput
);
715 Jim_DecrRefCount(interp
, tclOutput
);
720 /* sleep command sleeps for <n> miliseconds
721 * this is useful in target startup scripts
723 COMMAND_HANDLER(handle_sleep_command
)
728 if (strcmp(args
[1], "busy") == 0)
731 return ERROR_COMMAND_SYNTAX_ERROR
;
733 else if (argc
< 1 || argc
> 2)
734 return ERROR_COMMAND_SYNTAX_ERROR
;
736 unsigned long duration
= 0;
737 int retval
= parse_ulong(args
[0], &duration
);
738 if (ERROR_OK
!= retval
)
743 long long then
= timeval_ms();
744 while (timeval_ms() - then
< (long long)duration
)
746 target_call_timer_callbacks_now();
751 busy_sleep(duration
);
756 COMMAND_HANDLER(handle_fast_command
)
759 return ERROR_COMMAND_SYNTAX_ERROR
;
761 fast_and_dangerous
= strcmp("enable", args
[0]) == 0;
767 command_context_t
* command_init()
769 command_context_t
* context
= malloc(sizeof(command_context_t
));
770 extern const char startup_tcl
[];
773 context
->mode
= COMMAND_EXEC
;
774 context
->commands
= NULL
;
775 context
->current_target
= 0;
776 context
->output_handler
= NULL
;
777 context
->output_handler_priv
= NULL
;
781 /* Create an interpreter */
782 interp
= Jim_CreateInterp();
783 /* Add all the Jim core commands */
784 Jim_RegisterCoreCommands(interp
);
787 #if defined(_MSC_VER)
788 /* WinXX - is generic, the forward
789 * looking problem is this:
793 * "winxx" is generic.
796 #elif defined(__linux__)
798 #elif defined(__DARWIN__)
800 #elif defined(__CYGWIN__)
802 #elif defined(__MINGW32__)
804 #elif defined(__ECOS)
807 #warn unrecognized host OS...
810 Jim_SetGlobalVariableStr(interp
, "ocd_HOSTOS",
811 Jim_NewStringObj(interp
, HostOs
, strlen(HostOs
)));
813 Jim_CreateCommand(interp
, "ocd_find", jim_find
, NULL
, NULL
);
814 Jim_CreateCommand(interp
, "echo", jim_echo
, NULL
, NULL
);
815 Jim_CreateCommand(interp
, "capture", jim_capture
, NULL
, NULL
);
817 /* Set Jim's STDIO */
818 interp
->cookie_stdin
= interp
;
819 interp
->cookie_stdout
= interp
;
820 interp
->cookie_stderr
= interp
;
821 interp
->cb_fwrite
= openocd_jim_fwrite
;
822 interp
->cb_fread
= openocd_jim_fread
;
823 interp
->cb_vfprintf
= openocd_jim_vfprintf
;
824 interp
->cb_fflush
= openocd_jim_fflush
;
825 interp
->cb_fgets
= openocd_jim_fgets
;
828 Jim_EventLoopOnLoad(interp
);
830 if (Jim_Eval_Named(interp
, startup_tcl
, "embedded:startup.tcl",1) == JIM_ERR
)
832 LOG_ERROR("Failed to run startup.tcl (embedded into OpenOCD)");
833 Jim_PrintErrorMessage(interp
);
837 register_command(context
, NULL
, "sleep",
838 handle_sleep_command
, COMMAND_ANY
,
839 "<n> [busy] - sleep for n milliseconds. "
840 "\"busy\" means busy wait");
841 register_command(context
, NULL
, "fast",
842 handle_fast_command
, COMMAND_ANY
,
843 "fast <enable/disable> - place at beginning of "
844 "config files. Sets defaults to fast and dangerous.");
849 int command_context_mode(command_context_t
*cmd_ctx
, enum command_mode mode
)
852 return ERROR_INVALID_ARGUMENTS
;
854 cmd_ctx
->mode
= mode
;
858 void process_jim_events(void)
861 static int recursion
= 0;
866 Jim_ProcessEvents (interp
, JIM_ALL_EVENTS
| JIM_DONT_WAIT
);
872 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
)
874 Jim_CreateCommand(interp
, name
, cmd
, NULL
, NULL
);
876 Jim_Obj
*cmd_list
= Jim_NewListObj(interp
, NULL
, 0);
877 Jim_ListAppendElement(interp
, cmd_list
,
878 Jim_NewStringObj(interp
, name
, -1));
880 command_helptext_add(cmd_list
, help
);
883 /* return global variable long value or 0 upon failure */
884 long jim_global_long(const char *variable
)
886 Jim_Obj
*objPtr
= Jim_GetGlobalVariableStr(interp
, variable
, JIM_ERRMSG
);
888 if (Jim_GetLong(interp
, objPtr
, &t
) == JIM_OK
)
895 #define DEFINE_PARSE_NUM_TYPE(name, type, func, min, max) \
896 int parse##name(const char *str, type *ul) \
900 LOG_ERROR("Invalid command argument"); \
901 return ERROR_COMMAND_ARGUMENT_INVALID; \
904 *ul = func(str, &end, 0); \
907 LOG_ERROR("Invalid command argument"); \
908 return ERROR_COMMAND_ARGUMENT_INVALID; \
910 if ((max == *ul) && (ERANGE == errno)) \
912 LOG_ERROR("Argument overflow"); \
913 return ERROR_COMMAND_ARGUMENT_OVERFLOW; \
915 if (min && (min == *ul) && (ERANGE == errno)) \
917 LOG_ERROR("Argument underflow"); \
918 return ERROR_COMMAND_ARGUMENT_UNDERFLOW; \
922 DEFINE_PARSE_NUM_TYPE(_ulong
, unsigned long , strtoul
, 0, ULONG_MAX
)
923 DEFINE_PARSE_NUM_TYPE(_ullong
, unsigned long long, strtoull
, 0, ULLONG_MAX
)
924 DEFINE_PARSE_NUM_TYPE(_long
, long , strtol
, LONG_MIN
, LONG_MAX
)
925 DEFINE_PARSE_NUM_TYPE(_llong
, long long, strtoll
, LLONG_MIN
, LLONG_MAX
)
927 #define DEFINE_PARSE_WRAPPER(name, type, min, max, functype, funcname) \
928 int parse##name(const char *str, type *ul) \
931 int retval = parse##funcname(str, &n); \
932 if (ERROR_OK != retval) \
935 return ERROR_COMMAND_ARGUMENT_OVERFLOW; \
937 return ERROR_COMMAND_ARGUMENT_UNDERFLOW; \
942 #define DEFINE_PARSE_ULONG(name, type, min, max) \
943 DEFINE_PARSE_WRAPPER(name, type, min, max, unsigned long, _ulong)
944 DEFINE_PARSE_ULONG(_uint
, unsigned, 0, UINT_MAX
)
945 DEFINE_PARSE_ULONG(_u32
, uint32_t, 0, UINT32_MAX
)
946 DEFINE_PARSE_ULONG(_u16
, uint16_t, 0, UINT16_MAX
)
947 DEFINE_PARSE_ULONG(_u8
, uint8_t, 0, UINT8_MAX
)
949 #define DEFINE_PARSE_LONG(name, type, min, max) \
950 DEFINE_PARSE_WRAPPER(name, type, min, max, long, _long)
951 DEFINE_PARSE_LONG(_int
, int, n
< INT_MIN
, INT_MAX
)
952 DEFINE_PARSE_LONG(_s32
, int32_t, n
< INT32_MIN
, INT32_MAX
)
953 DEFINE_PARSE_LONG(_s16
, int16_t, n
< INT16_MIN
, INT16_MAX
)
954 DEFINE_PARSE_LONG(_s8
, int8_t, n
< INT8_MIN
, INT8_MAX
)
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)