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 Jim_Interp
*interp
= NULL
;
49 static int run_command(struct command_context
*context
,
50 struct command
*c
, const char *words
[], unsigned num_words
);
52 static void tcl_output(void *privData
, const char *file
, unsigned line
,
53 const char *function
, const char *string
)
55 Jim_Obj
*tclOutput
= (Jim_Obj
*)privData
;
56 Jim_AppendString(interp
, tclOutput
, string
, strlen(string
));
59 extern struct command_context
*global_cmd_ctx
;
61 void script_debug(Jim_Interp
*interp
, const char *name
,
62 unsigned argc
, Jim_Obj
*const *argv
)
64 LOG_DEBUG("command - %s", name
);
65 for (unsigned i
= 0; i
< argc
; i
++)
68 const char *w
= Jim_GetString(argv
[i
], &len
);
70 /* end of line comment? */
74 LOG_DEBUG("%s - argv[%d]=%s", name
, i
, w
);
78 static void script_command_args_free(const char **words
, unsigned nwords
)
80 for (unsigned i
= 0; i
< nwords
; i
++)
81 free((void *)words
[i
]);
84 static const char **script_command_args_alloc(
85 unsigned argc
, Jim_Obj
*const *argv
, unsigned *nwords
)
87 const char **words
= malloc(argc
* sizeof(char *));
92 for (i
= 0; i
< argc
; i
++)
95 const char *w
= Jim_GetString(argv
[i
], &len
);
96 /* a comment may end the line early */
100 words
[i
] = strdup(w
);
101 if (words
[i
] == NULL
)
103 script_command_args_free(words
, i
);
111 static int script_command(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
113 /* the private data is stashed in the interp structure */
115 struct command_context
*context
;
118 /* DANGER!!!! be careful what we invoke here, since interp->cmdPrivData might
119 * get overwritten by running other Jim commands! Treat it as an
120 * emphemeral global variable that is used in lieu of an argument
121 * to the fn and fish it out manually.
123 c
= interp
->cmdPrivData
;
126 LOG_ERROR("BUG: interp->cmdPrivData == NULL");
129 target_call_timer_callbacks_now();
130 LOG_USER_N("%s", ""); /* Keep GDB connection alive*/
132 script_debug(interp
, c
->name
, argc
, argv
);
135 const char **words
= script_command_args_alloc(argc
, argv
, &nwords
);
139 /* grab the command context from the associated data */
140 context
= Jim_GetAssocData(interp
, "context");
143 /* Tcl can invoke commands directly instead of via command_run_line(). This would
144 * happen when the Jim Tcl interpreter is provided by eCos.
146 context
= global_cmd_ctx
;
149 /* capture log output and return it */
150 Jim_Obj
*tclOutput
= Jim_NewStringObj(interp
, "", 0);
151 /* a garbage collect can happen, so we need a reference count to this object */
152 Jim_IncrRefCount(tclOutput
);
154 log_add_callback(tcl_output
, tclOutput
);
156 retval
= run_command(context
, c
, (const char **)words
, nwords
);
158 log_remove_callback(tcl_output
, tclOutput
);
160 /* We dump output into this local variable */
161 Jim_SetResult(interp
, tclOutput
);
162 Jim_DecrRefCount(interp
, tclOutput
);
164 script_command_args_free(words
, nwords
);
166 int *return_retval
= Jim_GetAssocData(interp
, "retval");
167 if (return_retval
!= NULL
)
169 *return_retval
= retval
;
172 return (retval
== ERROR_OK
)?JIM_OK
:JIM_ERR
;
175 static Jim_Obj
*command_name_list(struct command
*c
)
177 Jim_Obj
*cmd_list
= c
->parent
?
178 command_name_list(c
->parent
) :
179 Jim_NewListObj(interp
, NULL
, 0);
180 Jim_ListAppendElement(interp
, cmd_list
,
181 Jim_NewStringObj(interp
, c
->name
, -1));
186 static void command_helptext_add(Jim_Obj
*cmd_list
, const char *help
)
188 Jim_Obj
*cmd_entry
= Jim_NewListObj(interp
, NULL
, 0);
189 Jim_ListAppendElement(interp
, cmd_entry
, cmd_list
);
190 Jim_ListAppendElement(interp
, cmd_entry
,
191 Jim_NewStringObj(interp
, help
? : "", -1));
193 /* accumulate help text in Tcl helptext list. */
194 Jim_Obj
*helptext
= Jim_GetGlobalVariableStr(interp
,
195 "ocd_helptext", JIM_ERRMSG
);
196 if (Jim_IsShared(helptext
))
197 helptext
= Jim_DuplicateObj(interp
, helptext
);
198 Jim_ListAppendElement(interp
, helptext
, cmd_entry
);
201 /* nice short description of source file */
202 #define __THIS__FILE__ "command.c"
205 * Find a command by name from a list of commands.
206 * @returns The named command if found, or NULL.
208 static struct command
*command_find(struct command
**head
, const char *name
)
211 for (struct command
*cc
= *head
; cc
; cc
= cc
->next
)
213 if (strcmp(cc
->name
, name
) == 0)
220 * Add the command to the end of linked list.
221 * @returns Returns false if the named command already exists in the list.
222 * Returns true otherwise.
224 static void command_add_child(struct command
**head
, struct command
*c
)
232 struct command
*cc
= *head
;
233 while (cc
->next
) cc
= cc
->next
;
237 struct command
* register_command(struct command_context
*context
,
238 struct command
*parent
, char *name
, command_handler_t handler
,
239 enum command_mode mode
, char *help
)
241 if (!context
|| !name
)
244 struct command
**head
= parent
? &parent
->children
: &context
->commands
;
245 struct command
*c
= command_find(head
, name
);
249 c
= malloc(sizeof(struct command
));
251 c
->name
= strdup(name
);
254 c
->handler
= handler
;
258 command_add_child(head
, c
);
260 command_helptext_add(command_name_list(c
), help
);
262 /* just a placeholder, no handler */
263 if (c
->handler
== NULL
)
266 const char *full_name
= command_name(c
, '_');
268 const char *ocd_name
= alloc_printf("ocd_%s", full_name
);
269 Jim_CreateCommand(interp
, ocd_name
, script_command
, c
, NULL
);
270 free((void *)ocd_name
);
272 /* we now need to add an overrideable proc */
273 const char *override_name
= alloc_printf("proc %s {args} {"
274 "if {[catch {eval ocd_%s $args}] == 0} "
275 "{return \"\"} else {return -code error}}",
276 full_name
, full_name
);
277 Jim_Eval_Named(interp
, override_name
, __THIS__FILE__
, __LINE__
);
278 free((void *)override_name
);
280 free((void *)full_name
);
285 int unregister_all_commands(struct command_context
*context
)
287 struct command
*c
, *c2
;
292 while (NULL
!= context
->commands
)
294 c
= context
->commands
;
296 while (NULL
!= c
->children
)
299 c
->children
= c
->children
->next
;
306 context
->commands
= context
->commands
->next
;
317 int unregister_command(struct command_context
*context
, char *name
)
319 struct command
*c
, *p
= NULL
, *c2
;
321 if ((!context
) || (!name
))
322 return ERROR_INVALID_ARGUMENTS
;
325 c
= context
->commands
;
329 if (strcmp(name
, c
->name
) == 0)
338 /* first element in command list */
339 context
->commands
= c
->next
;
342 /* unregister children */
343 while (NULL
!= c
->children
)
346 c
->children
= c
->children
->next
;
361 /* remember the last command for unlinking */
369 void command_output_text(struct command_context
*context
, const char *data
)
371 if (context
&& context
->output_handler
&& data
) {
372 context
->output_handler(context
, data
);
376 void command_print_sameline(struct command_context
*context
, const char *format
, ...)
381 va_start(ap
, format
);
383 string
= alloc_vprintf(format
, ap
);
386 /* we want this collected in the log + we also want to pick it up as a tcl return
389 * The latter bit isn't precisely neat, but will do for now.
391 LOG_USER_N("%s", string
);
392 /* We already printed it above */
393 /* command_output_text(context, string); */
400 void command_print(struct command_context
*context
, const char *format
, ...)
405 va_start(ap
, format
);
407 string
= alloc_vprintf(format
, ap
);
410 strcat(string
, "\n"); /* alloc_vprintf guaranteed the buffer to be at least one char longer */
411 /* we want this collected in the log + we also want to pick it up as a tcl return
414 * The latter bit isn't precisely neat, but will do for now.
416 LOG_USER_N("%s", string
);
417 /* We already printed it above */
418 /* command_output_text(context, string); */
425 static char *__command_name(struct command
*c
, char delim
, unsigned extra
)
428 unsigned len
= strlen(c
->name
);
429 if (NULL
== c
->parent
) {
430 // allocate enough for the name, child names, and '\0'
431 name
= malloc(len
+ extra
+ 1);
432 strcpy(name
, c
->name
);
434 // parent's extra must include both the space and name
435 name
= __command_name(c
->parent
, delim
, 1 + len
+ extra
);
436 char dstr
[2] = { delim
, 0 };
438 strcat(name
, c
->name
);
442 char *command_name(struct command
*c
, char delim
)
444 return __command_name(c
, delim
, 0);
447 static int run_command(struct command_context
*context
,
448 struct command
*c
, const char *words
[], unsigned num_words
)
450 if (!((context
->mode
== COMMAND_CONFIG
) || (c
->mode
== COMMAND_ANY
) || (c
->mode
== context
->mode
)))
452 /* Config commands can not run after the config stage */
453 LOG_ERROR("Command '%s' only runs during configuration stage", c
->name
);
457 struct command_invocation cmd
= {
460 .argc
= num_words
- 1,
463 int retval
= c
->handler(&cmd
);
464 if (retval
== ERROR_COMMAND_SYNTAX_ERROR
)
466 /* Print help for command */
467 char *full_name
= command_name(c
, ' ');
468 if (NULL
!= full_name
) {
469 command_run_linef(context
, "help %s", full_name
);
474 else if (retval
== ERROR_COMMAND_CLOSE_CONNECTION
)
476 /* just fall through for a shutdown request */
478 else if (retval
!= ERROR_OK
)
480 /* we do not print out an error message because the command *should*
481 * have printed out an error
483 LOG_DEBUG("Command failed with error code %d", retval
);
489 int command_run_line(struct command_context
*context
, char *line
)
491 /* all the parent commands have been registered with the interpreter
492 * so, can just evaluate the line as a script and check for
495 /* run the line thru a script engine */
496 int retval
= ERROR_FAIL
;
498 /* Beware! This code needs to be reentrant. It is also possible
499 * for OpenOCD commands to be invoked directly from Tcl. This would
500 * happen when the Jim Tcl interpreter is provided by eCos for
503 Jim_DeleteAssocData(interp
, "context");
504 retcode
= Jim_SetAssocData(interp
, "context", NULL
, context
);
505 if (retcode
== JIM_OK
)
507 /* associated the return value */
508 Jim_DeleteAssocData(interp
, "retval");
509 retcode
= Jim_SetAssocData(interp
, "retval", NULL
, &retval
);
510 if (retcode
== JIM_OK
)
512 retcode
= Jim_Eval_Named(interp
, line
, __THIS__FILE__
, __LINE__
);
514 Jim_DeleteAssocData(interp
, "retval");
516 Jim_DeleteAssocData(interp
, "context");
518 if (retcode
== JIM_ERR
) {
519 if (retval
!= ERROR_COMMAND_CLOSE_CONNECTION
)
521 /* We do not print the connection closed error message */
522 Jim_PrintErrorMessage(interp
);
524 if (retval
== ERROR_OK
)
526 /* It wasn't a low level OpenOCD command that failed */
530 } else if (retcode
== JIM_EXIT
) {
532 /* exit(Jim_GetExitCode(interp)); */
537 result
= Jim_GetString(Jim_GetResult(interp
), &reslen
);
542 for (i
= 0; i
< reslen
; i
+= 256)
548 strncpy(buff
, result
+ i
, chunk
);
550 LOG_USER_N("%s", buff
);
552 LOG_USER_N("%s", "\n");
559 int command_run_linef(struct command_context
*context
, const char *format
, ...)
561 int retval
= ERROR_FAIL
;
564 va_start(ap
, format
);
565 string
= alloc_vprintf(format
, ap
);
568 retval
= command_run_line(context
, string
);
574 void command_set_output_handler(struct command_context
* context
,
575 command_output_handler_t output_handler
, void *priv
)
577 context
->output_handler
= output_handler
;
578 context
->output_handler_priv
= priv
;
581 struct command_context
* copy_command_context(struct command_context
* context
)
583 struct command_context
* copy_context
= malloc(sizeof(struct command_context
));
585 *copy_context
= *context
;
590 int command_done(struct command_context
*context
)
598 /* find full path to file */
599 static int jim_find(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
603 const char *file
= Jim_GetString(argv
[1], NULL
);
604 char *full_path
= find_file(file
);
605 if (full_path
== NULL
)
607 Jim_Obj
*result
= Jim_NewStringObj(interp
, full_path
, strlen(full_path
));
610 Jim_SetResult(interp
, result
);
614 static int jim_echo(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
618 const char *str
= Jim_GetString(argv
[1], NULL
);
623 static size_t openocd_jim_fwrite(const void *_ptr
, size_t size
, size_t n
, void *cookie
)
629 /* make it a char easier to read code */
633 if (ptr
== NULL
|| interp
== NULL
|| nbytes
== 0) {
637 /* do we have to chunk it? */
638 if (ptr
[nbytes
] == 0)
640 /* no it is a C style string */
641 LOG_USER_N("%s", ptr
);
644 /* GRR we must chunk - not null terminated */
654 memcpy(chunk
, ptr
, x
);
658 LOG_USER_N("%s", chunk
);
666 static size_t openocd_jim_fread(void *ptr
, size_t size
, size_t n
, void *cookie
)
668 /* TCL wants to read... tell him no */
672 static int openocd_jim_vfprintf(void *cookie
, const char *fmt
, va_list ap
)
683 cp
= alloc_vprintf(fmt
, ap
);
686 LOG_USER_N("%s", cp
);
693 static int openocd_jim_fflush(void *cookie
)
695 /* nothing to flush */
699 static char* openocd_jim_fgets(char *s
, int size
, void *cookie
)
706 static int jim_capture(Jim_Interp
*interp
, int argc
, Jim_Obj
*const *argv
)
711 const char *str
= Jim_GetString(argv
[1], NULL
);
713 /* capture log output and return it */
714 Jim_Obj
*tclOutput
= Jim_NewStringObj(interp
, "", 0);
715 /* a garbage collect can happen, so we need a reference count to this object */
716 Jim_IncrRefCount(tclOutput
);
718 log_add_callback(tcl_output
, tclOutput
);
720 retcode
= Jim_Eval_Named(interp
, str
, __THIS__FILE__
, __LINE__
);
722 log_remove_callback(tcl_output
, tclOutput
);
724 /* We dump output into this local variable */
725 Jim_SetResult(interp
, tclOutput
);
726 Jim_DecrRefCount(interp
, tclOutput
);
731 /* sleep command sleeps for <n> miliseconds
732 * this is useful in target startup scripts
734 COMMAND_HANDLER(handle_sleep_command
)
739 if (strcmp(CMD_ARGV
[1], "busy") == 0)
742 return ERROR_COMMAND_SYNTAX_ERROR
;
744 else if (CMD_ARGC
< 1 || CMD_ARGC
> 2)
745 return ERROR_COMMAND_SYNTAX_ERROR
;
747 unsigned long duration
= 0;
748 int retval
= parse_ulong(CMD_ARGV
[0], &duration
);
749 if (ERROR_OK
!= retval
)
754 long long then
= timeval_ms();
755 while (timeval_ms() - then
< (long long)duration
)
757 target_call_timer_callbacks_now();
762 busy_sleep(duration
);
767 struct command_context
* command_init(const char *startup_tcl
)
769 struct command_context
* context
= malloc(sizeof(struct command_context
));
772 context
->mode
= COMMAND_EXEC
;
773 context
->commands
= NULL
;
774 context
->current_target
= 0;
775 context
->output_handler
= NULL
;
776 context
->output_handler_priv
= NULL
;
780 /* Create an interpreter */
781 interp
= Jim_CreateInterp();
782 /* Add all the Jim core commands */
783 Jim_RegisterCoreCommands(interp
);
786 #if defined(_MSC_VER)
787 /* WinXX - is generic, the forward
788 * looking problem is this:
792 * "winxx" is generic.
795 #elif defined(__linux__)
797 #elif defined(__DARWIN__)
799 #elif defined(__CYGWIN__)
801 #elif defined(__MINGW32__)
803 #elif defined(__ECOS)
806 #warn unrecognized host OS...
809 Jim_SetGlobalVariableStr(interp
, "ocd_HOSTOS",
810 Jim_NewStringObj(interp
, HostOs
, strlen(HostOs
)));
812 Jim_CreateCommand(interp
, "ocd_find", jim_find
, NULL
, NULL
);
813 Jim_CreateCommand(interp
, "echo", jim_echo
, NULL
, NULL
);
814 Jim_CreateCommand(interp
, "capture", jim_capture
, NULL
, NULL
);
816 /* Set Jim's STDIO */
817 interp
->cookie_stdin
= interp
;
818 interp
->cookie_stdout
= interp
;
819 interp
->cookie_stderr
= interp
;
820 interp
->cb_fwrite
= openocd_jim_fwrite
;
821 interp
->cb_fread
= openocd_jim_fread
;
822 interp
->cb_vfprintf
= openocd_jim_vfprintf
;
823 interp
->cb_fflush
= openocd_jim_fflush
;
824 interp
->cb_fgets
= openocd_jim_fgets
;
827 Jim_EventLoopOnLoad(interp
);
829 if (Jim_Eval_Named(interp
, startup_tcl
, "embedded:startup.tcl",1) == JIM_ERR
)
831 LOG_ERROR("Failed to run startup.tcl (embedded into OpenOCD)");
832 Jim_PrintErrorMessage(interp
);
836 register_command(context
, NULL
, "sleep",
837 handle_sleep_command
, COMMAND_ANY
,
838 "<n> [busy] - sleep for n milliseconds. "
839 "\"busy\" means busy wait");
844 int command_context_mode(struct command_context
*cmd_ctx
, enum command_mode mode
)
847 return ERROR_INVALID_ARGUMENTS
;
849 cmd_ctx
->mode
= mode
;
853 void process_jim_events(void)
856 static int recursion
= 0;
861 Jim_ProcessEvents (interp
, JIM_ALL_EVENTS
| JIM_DONT_WAIT
);
867 void register_jim(struct command_context
*cmd_ctx
, const char *name
,
868 Jim_CmdProc cmd
, const char *help
)
870 Jim_CreateCommand(interp
, name
, cmd
, NULL
, NULL
);
872 Jim_Obj
*cmd_list
= Jim_NewListObj(interp
, NULL
, 0);
873 Jim_ListAppendElement(interp
, cmd_list
,
874 Jim_NewStringObj(interp
, name
, -1));
876 command_helptext_add(cmd_list
, help
);
879 #define DEFINE_PARSE_NUM_TYPE(name, type, func, min, max) \
880 int parse##name(const char *str, type *ul) \
884 LOG_ERROR("Invalid command argument"); \
885 return ERROR_COMMAND_ARGUMENT_INVALID; \
888 *ul = func(str, &end, 0); \
891 LOG_ERROR("Invalid command argument"); \
892 return ERROR_COMMAND_ARGUMENT_INVALID; \
894 if ((max == *ul) && (ERANGE == errno)) \
896 LOG_ERROR("Argument overflow"); \
897 return ERROR_COMMAND_ARGUMENT_OVERFLOW; \
899 if (min && (min == *ul) && (ERANGE == errno)) \
901 LOG_ERROR("Argument underflow"); \
902 return ERROR_COMMAND_ARGUMENT_UNDERFLOW; \
906 DEFINE_PARSE_NUM_TYPE(_ulong
, unsigned long , strtoul
, 0, ULONG_MAX
)
907 DEFINE_PARSE_NUM_TYPE(_ullong
, unsigned long long, strtoull
, 0, ULLONG_MAX
)
908 DEFINE_PARSE_NUM_TYPE(_long
, long , strtol
, LONG_MIN
, LONG_MAX
)
909 DEFINE_PARSE_NUM_TYPE(_llong
, long long, strtoll
, LLONG_MIN
, LLONG_MAX
)
911 #define DEFINE_PARSE_WRAPPER(name, type, min, max, functype, funcname) \
912 int parse##name(const char *str, type *ul) \
915 int retval = parse##funcname(str, &n); \
916 if (ERROR_OK != retval) \
919 return ERROR_COMMAND_ARGUMENT_OVERFLOW; \
921 return ERROR_COMMAND_ARGUMENT_UNDERFLOW; \
926 #define DEFINE_PARSE_ULONG(name, type, min, max) \
927 DEFINE_PARSE_WRAPPER(name, type, min, max, unsigned long, _ulong)
928 DEFINE_PARSE_ULONG(_uint
, unsigned, 0, UINT_MAX
)
929 DEFINE_PARSE_ULONG(_u32
, uint32_t, 0, UINT32_MAX
)
930 DEFINE_PARSE_ULONG(_u16
, uint16_t, 0, UINT16_MAX
)
931 DEFINE_PARSE_ULONG(_u8
, uint8_t, 0, UINT8_MAX
)
933 #define DEFINE_PARSE_LONG(name, type, min, max) \
934 DEFINE_PARSE_WRAPPER(name, type, min, max, long, _long)
935 DEFINE_PARSE_LONG(_int
, int, n
< INT_MIN
, INT_MAX
)
936 DEFINE_PARSE_LONG(_s32
, int32_t, n
< INT32_MIN
, INT32_MAX
)
937 DEFINE_PARSE_LONG(_s16
, int16_t, n
< INT16_MIN
, INT16_MAX
)
938 DEFINE_PARSE_LONG(_s8
, int8_t, n
< INT8_MIN
, INT8_MAX
)
940 static int command_parse_bool(const char *in
, bool *out
,
941 const char *on
, const char *off
)
943 if (strcasecmp(in
, on
) == 0)
945 else if (strcasecmp(in
, off
) == 0)
948 return ERROR_COMMAND_SYNTAX_ERROR
;
952 int command_parse_bool_arg(const char *in
, bool *out
)
954 if (command_parse_bool(in
, out
, "on", "off") == ERROR_OK
)
956 if (command_parse_bool(in
, out
, "enable", "disable") == ERROR_OK
)
958 if (command_parse_bool(in
, out
, "true", "false") == ERROR_OK
)
960 if (command_parse_bool(in
, out
, "yes", "no") == ERROR_OK
)
962 if (command_parse_bool(in
, out
, "1", "0") == ERROR_OK
)
964 return ERROR_INVALID_ARGUMENTS
;
967 COMMAND_HELPER(handle_command_parse_bool
, bool *out
, const char *label
)
971 const char *in
= CMD_ARGV
[0];
972 if (command_parse_bool_arg(in
, out
) != ERROR_OK
)
974 LOG_ERROR("%s: argument '%s' is not valid", CMD_NAME
, in
);
975 return ERROR_INVALID_ARGUMENTS
;
980 LOG_INFO("%s is %s", label
, *out
? "enabled" : "disabled");
983 return ERROR_INVALID_ARGUMENTS
;
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)