When calling openocd from a shell like this:
[openocd.git] / src / helper / command.c
1 /***************************************************************************
2 * Copyright (C) 2005 by Dominic Rath *
3 * Dominic.Rath@gmx.de *
4 * *
5 * Copyright (C) 2007,2008 Øyvind Harboe *
6 * oyvind.harboe@zylin.com *
7 * *
8 * Copyright (C) 2008, Duane Ellis *
9 * openocd@duaneeellis.com *
10 * *
11 * part of this file is taken from libcli (libcli.sourceforge.net) *
12 * Copyright (C) David Parrish (david@dparrish.com) *
13 * *
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. *
18 * *
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. *
23 * *
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 ***************************************************************************/
29
30 #ifdef HAVE_CONFIG_H
31 #include "config.h"
32 #endif
33
34 /* see Embedder-HOWTO.txt in Jim Tcl project hosted on BerliOS*/
35 #define JIM_EMBEDDED
36
37 /* @todo the inclusion of target.h here is a layering violation */
38 #include <jtag/jtag.h>
39 #include <target/target.h>
40 #include "command.h"
41 #include "configuration.h"
42 #include "log.h"
43 #include "time_support.h"
44 #include "jim-eventloop.h"
45
46 /* nice short description of source file */
47 #define __THIS__FILE__ "command.c"
48
49 static int run_command(struct command_context *context,
50 struct command *c, const char *words[], unsigned num_words);
51
52 struct log_capture_state {
53 Jim_Interp *interp;
54 Jim_Obj *output;
55 };
56
57 static void tcl_output(void *privData, const char *file, unsigned line,
58 const char *function, const char *string)
59 {
60 struct log_capture_state *state = (struct log_capture_state *)privData;
61 Jim_AppendString(state->interp, state->output, string, strlen(string));
62 }
63
64 static struct log_capture_state *command_log_capture_start(Jim_Interp *interp)
65 {
66 /* capture log output and return it. A garbage collect can
67 * happen, so we need a reference count to this object */
68 Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
69 if (NULL == tclOutput)
70 return NULL;
71
72 struct log_capture_state *state = malloc(sizeof(*state));
73 if (NULL == state)
74 return NULL;
75
76 state->interp = interp;
77 Jim_IncrRefCount(tclOutput);
78 state->output = tclOutput;
79
80 log_add_callback(tcl_output, state);
81
82 return state;
83 }
84
85 /* Classic openocd commands provide progress output which we
86 * will capture and return as a Tcl return value.
87 *
88 * However, if a non-openocd command has been invoked, then it
89 * makes sense to return the tcl return value from that command.
90 *
91 * The tcl return value is empty for openocd commands that provide
92 * progress output.
93 *
94 * Therefore we set the tcl return value only if we actually
95 * captured output.
96 */
97 static void command_log_capture_finish(struct log_capture_state *state)
98 {
99 if (NULL == state)
100 return;
101
102 log_remove_callback(tcl_output, state);
103
104 int length;
105 Jim_GetString(state->output, &length);
106
107 if (length > 0)
108 Jim_SetResult(state->interp, state->output);
109 else {
110 /* No output captured, use tcl return value (which could
111 * be empty too). */
112 }
113 Jim_DecrRefCount(state->interp, state->output);
114
115 free(state);
116 }
117
118 static int command_retval_set(Jim_Interp *interp, int retval)
119 {
120 int *return_retval = Jim_GetAssocData(interp, "retval");
121 if (retval == ERROR_COMMAND_CLOSE_CONNECTION) {
122 if (return_retval != NULL)
123 *return_retval = 0;
124 return JIM_EXIT;
125 } else {
126 if (return_retval != NULL)
127 *return_retval = retval;
128 return (retval == ERROR_OK) ? JIM_OK : JIM_ERR;
129 }
130 }
131
132 extern struct command_context *global_cmd_ctx;
133
134 /* dump a single line to the log for the command.
135 * Do nothing in case we are not at debug level 3 */
136 void script_debug(Jim_Interp *interp, const char *name,
137 unsigned argc, Jim_Obj * const *argv)
138 {
139 if (debug_level < LOG_LVL_DEBUG)
140 return;
141
142 char *dbg = alloc_printf("command - %s", name);
143 for (unsigned i = 0; i < argc; i++) {
144 int len;
145 const char *w = Jim_GetString(argv[i], &len);
146 char *t = alloc_printf("%s %s", dbg, w);
147 free(dbg);
148 dbg = t;
149 }
150 LOG_DEBUG("%s", dbg);
151 free(dbg);
152 }
153
154 static void script_command_args_free(const char **words, unsigned nwords)
155 {
156 for (unsigned i = 0; i < nwords; i++)
157 free((void *)words[i]);
158 free(words);
159 }
160 static const char **script_command_args_alloc(
161 unsigned argc, Jim_Obj * const *argv, unsigned *nwords)
162 {
163 const char **words = malloc(argc * sizeof(char *));
164 if (NULL == words)
165 return NULL;
166
167 unsigned i;
168 for (i = 0; i < argc; i++) {
169 int len;
170 const char *w = Jim_GetString(argv[i], &len);
171 words[i] = strdup(w);
172 if (words[i] == NULL) {
173 script_command_args_free(words, i);
174 return NULL;
175 }
176 }
177 *nwords = i;
178 return words;
179 }
180
181 struct command_context *current_command_context(Jim_Interp *interp)
182 {
183 /* grab the command context from the associated data */
184 struct command_context *cmd_ctx = Jim_GetAssocData(interp, "context");
185 if (NULL == cmd_ctx) {
186 /* Tcl can invoke commands directly instead of via command_run_line(). This would
187 * happen when the Jim Tcl interpreter is provided by eCos or if we are running
188 * commands in a startup script.
189 *
190 * A telnet or gdb server would provide a non-default command context to
191 * handle piping of error output, have a separate current target, etc.
192 */
193 cmd_ctx = global_cmd_ctx;
194 }
195 return cmd_ctx;
196 }
197
198 static int script_command_run(Jim_Interp *interp,
199 int argc, Jim_Obj * const *argv, struct command *c, bool capture)
200 {
201 target_call_timer_callbacks_now();
202 LOG_USER_N("%s", ""); /* Keep GDB connection alive*/
203
204 unsigned nwords;
205 const char **words = script_command_args_alloc(argc, argv, &nwords);
206 if (NULL == words)
207 return JIM_ERR;
208
209 struct log_capture_state *state = NULL;
210 if (capture)
211 state = command_log_capture_start(interp);
212
213 struct command_context *cmd_ctx = current_command_context(interp);
214 int retval = run_command(cmd_ctx, c, (const char **)words, nwords);
215
216 command_log_capture_finish(state);
217
218 script_command_args_free(words, nwords);
219 return command_retval_set(interp, retval);
220 }
221
222 static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
223 {
224 /* the private data is stashed in the interp structure */
225
226 struct command *c = interp->cmdPrivData;
227 assert(c);
228 script_debug(interp, c->name, argc, argv);
229 return script_command_run(interp, argc, argv, c, true);
230 }
231
232 static struct command *command_root(struct command *c)
233 {
234 while (NULL != c->parent)
235 c = c->parent;
236 return c;
237 }
238
239 /**
240 * Find a command by name from a list of commands.
241 * @returns Returns the named command if it exists in the list.
242 * Returns NULL otherwise.
243 */
244 static struct command *command_find(struct command *head, const char *name)
245 {
246 for (struct command *cc = head; cc; cc = cc->next) {
247 if (strcmp(cc->name, name) == 0)
248 return cc;
249 }
250 return NULL;
251 }
252 struct command *command_find_in_context(struct command_context *cmd_ctx,
253 const char *name)
254 {
255 return command_find(cmd_ctx->commands, name);
256 }
257 struct command *command_find_in_parent(struct command *parent,
258 const char *name)
259 {
260 return command_find(parent->children, name);
261 }
262
263 /**
264 * Add the command into the linked list, sorted by name.
265 * @param head Address to head of command list pointer, which may be
266 * updated if @c c gets inserted at the beginning of the list.
267 * @param c The command to add to the list pointed to by @c head.
268 */
269 static void command_add_child(struct command **head, struct command *c)
270 {
271 assert(head);
272 if (NULL == *head) {
273 *head = c;
274 return;
275 }
276
277 while ((*head)->next && (strcmp(c->name, (*head)->name) > 0))
278 head = &(*head)->next;
279
280 if (strcmp(c->name, (*head)->name) > 0) {
281 c->next = (*head)->next;
282 (*head)->next = c;
283 } else {
284 c->next = *head;
285 *head = c;
286 }
287 }
288
289 static struct command **command_list_for_parent(
290 struct command_context *cmd_ctx, struct command *parent)
291 {
292 return parent ? &parent->children : &cmd_ctx->commands;
293 }
294
295 static void command_free(struct command *c)
296 {
297 /** @todo if command has a handler, unregister its jim command! */
298
299 while (NULL != c->children) {
300 struct command *tmp = c->children;
301 c->children = tmp->next;
302 command_free(tmp);
303 }
304
305 if (c->name)
306 free((void *)c->name);
307 if (c->help)
308 free((void *)c->help);
309 if (c->usage)
310 free((void *)c->usage);
311 free(c);
312 }
313
314 static struct command *command_new(struct command_context *cmd_ctx,
315 struct command *parent, const struct command_registration *cr)
316 {
317 assert(cr->name);
318
319 /*
320 * If it is a non-jim command with no .usage specified,
321 * log an error.
322 *
323 * strlen(.usage) == 0 means that the command takes no
324 * arguments.
325 */
326 if ((cr->jim_handler == NULL) && (cr->usage == NULL)) {
327 LOG_DEBUG("BUG: command '%s%s%s' does not have the "
328 "'.usage' field filled out",
329 parent && parent->name ? parent->name : "",
330 parent && parent->name ? " " : "",
331 cr->name);
332 }
333
334 struct command *c = calloc(1, sizeof(struct command));
335 if (NULL == c)
336 return NULL;
337
338 c->name = strdup(cr->name);
339 if (cr->help)
340 c->help = strdup(cr->help);
341 if (cr->usage)
342 c->usage = strdup(cr->usage);
343
344 if (!c->name || (cr->help && !c->help) || (cr->usage && !c->usage))
345 goto command_new_error;
346
347 c->parent = parent;
348 c->handler = cr->handler;
349 c->jim_handler = cr->jim_handler;
350 c->jim_handler_data = cr->jim_handler_data;
351 c->mode = cr->mode;
352
353 command_add_child(command_list_for_parent(cmd_ctx, parent), c);
354
355 return c;
356
357 command_new_error:
358 command_free(c);
359 return NULL;
360 }
361
362 static int command_unknown(Jim_Interp *interp, int argc, Jim_Obj *const *argv);
363
364 static int register_command_handler(struct command_context *cmd_ctx,
365 struct command *c)
366 {
367 Jim_Interp *interp = cmd_ctx->interp;
368 const char *ocd_name = alloc_printf("ocd_%s", c->name);
369 if (NULL == ocd_name)
370 return JIM_ERR;
371
372 LOG_DEBUG("registering '%s'...", ocd_name);
373
374 Jim_CmdProc func = c->handler ? &script_command : &command_unknown;
375 int retval = Jim_CreateCommand(interp, ocd_name, func, c, NULL);
376 free((void *)ocd_name);
377 if (JIM_OK != retval)
378 return retval;
379
380 /* we now need to add an overrideable proc */
381 const char *override_name = alloc_printf(
382 "proc %s {args} {eval ocd_bouncer %s $args}",
383 c->name, c->name);
384 if (NULL == override_name)
385 return JIM_ERR;
386
387 retval = Jim_Eval_Named(interp, override_name, 0, 0);
388 free((void *)override_name);
389
390 return retval;
391 }
392
393 struct command *register_command(struct command_context *context,
394 struct command *parent, const struct command_registration *cr)
395 {
396 if (!context || !cr->name)
397 return NULL;
398
399 const char *name = cr->name;
400 struct command **head = command_list_for_parent(context, parent);
401 struct command *c = command_find(*head, name);
402 if (NULL != c) {
403 /* TODO: originally we treated attempting to register a cmd twice as an error
404 * Sometimes we need this behaviour, such as with flash banks.
405 * http://www.mail-archive.com/openocd-development@lists.berlios.de/msg11152.html */
406 LOG_DEBUG("command '%s' is already registered in '%s' context",
407 name, parent ? parent->name : "<global>");
408 return c;
409 }
410
411 c = command_new(context, parent, cr);
412 if (NULL == c)
413 return NULL;
414
415 int retval = ERROR_OK;
416 if (NULL != cr->jim_handler && NULL == parent) {
417 retval = Jim_CreateCommand(context->interp, cr->name,
418 cr->jim_handler, cr->jim_handler_data, NULL);
419 } else if (NULL != cr->handler || NULL != parent)
420 retval = register_command_handler(context, command_root(c));
421
422 if (ERROR_OK != retval) {
423 unregister_command(context, parent, name);
424 c = NULL;
425 }
426 return c;
427 }
428
429 int register_commands(struct command_context *cmd_ctx, struct command *parent,
430 const struct command_registration *cmds)
431 {
432 int retval = ERROR_OK;
433 unsigned i;
434 for (i = 0; cmds[i].name || cmds[i].chain; i++) {
435 const struct command_registration *cr = cmds + i;
436
437 struct command *c = NULL;
438 if (NULL != cr->name) {
439 c = register_command(cmd_ctx, parent, cr);
440 if (NULL == c) {
441 retval = ERROR_FAIL;
442 break;
443 }
444 }
445 if (NULL != cr->chain) {
446 struct command *p = c ? : parent;
447 retval = register_commands(cmd_ctx, p, cr->chain);
448 if (ERROR_OK != retval)
449 break;
450 }
451 }
452 if (ERROR_OK != retval) {
453 for (unsigned j = 0; j < i; j++)
454 unregister_command(cmd_ctx, parent, cmds[j].name);
455 }
456 return retval;
457 }
458
459 int unregister_all_commands(struct command_context *context,
460 struct command *parent)
461 {
462 if (context == NULL)
463 return ERROR_OK;
464
465 struct command **head = command_list_for_parent(context, parent);
466 while (NULL != *head) {
467 struct command *tmp = *head;
468 *head = tmp->next;
469 command_free(tmp);
470 }
471
472 return ERROR_OK;
473 }
474
475 int unregister_command(struct command_context *context,
476 struct command *parent, const char *name)
477 {
478 if ((!context) || (!name))
479 return ERROR_COMMAND_SYNTAX_ERROR;
480
481 struct command *p = NULL;
482 struct command **head = command_list_for_parent(context, parent);
483 for (struct command *c = *head; NULL != c; p = c, c = c->next) {
484 if (strcmp(name, c->name) != 0)
485 continue;
486
487 if (p)
488 p->next = c->next;
489 else
490 *head = c->next;
491
492 command_free(c);
493 return ERROR_OK;
494 }
495
496 return ERROR_OK;
497 }
498
499 void command_set_handler_data(struct command *c, void *p)
500 {
501 if (NULL != c->handler || NULL != c->jim_handler)
502 c->jim_handler_data = p;
503 for (struct command *cc = c->children; NULL != cc; cc = cc->next)
504 command_set_handler_data(cc, p);
505 }
506
507 void command_output_text(struct command_context *context, const char *data)
508 {
509 if (context && context->output_handler && data)
510 context->output_handler(context, data);
511 }
512
513 void command_print_sameline(struct command_context *context, const char *format, ...)
514 {
515 char *string;
516
517 va_list ap;
518 va_start(ap, format);
519
520 string = alloc_vprintf(format, ap);
521 if (string != NULL) {
522 /* we want this collected in the log + we also want to pick it up as a tcl return
523 * value.
524 *
525 * The latter bit isn't precisely neat, but will do for now.
526 */
527 LOG_USER_N("%s", string);
528 /* We already printed it above
529 * command_output_text(context, string); */
530 free(string);
531 }
532
533 va_end(ap);
534 }
535
536 void command_print(struct command_context *context, const char *format, ...)
537 {
538 char *string;
539
540 va_list ap;
541 va_start(ap, format);
542
543 string = alloc_vprintf(format, ap);
544 if (string != NULL) {
545 strcat(string, "\n"); /* alloc_vprintf guaranteed the buffer to be at least one
546 *char longer */
547 /* we want this collected in the log + we also want to pick it up as a tcl return
548 * value.
549 *
550 * The latter bit isn't precisely neat, but will do for now.
551 */
552 LOG_USER_N("%s", string);
553 /* We already printed it above
554 * command_output_text(context, string); */
555 free(string);
556 }
557
558 va_end(ap);
559 }
560
561 static char *__command_name(struct command *c, char delim, unsigned extra)
562 {
563 char *name;
564 unsigned len = strlen(c->name);
565 if (NULL == c->parent) {
566 /* allocate enough for the name, child names, and '\0' */
567 name = malloc(len + extra + 1);
568 strcpy(name, c->name);
569 } else {
570 /* parent's extra must include both the space and name */
571 name = __command_name(c->parent, delim, 1 + len + extra);
572 char dstr[2] = { delim, 0 };
573 strcat(name, dstr);
574 strcat(name, c->name);
575 }
576 return name;
577 }
578 char *command_name(struct command *c, char delim)
579 {
580 return __command_name(c, delim, 0);
581 }
582
583 static bool command_can_run(struct command_context *cmd_ctx, struct command *c)
584 {
585 return c->mode == COMMAND_ANY || c->mode == cmd_ctx->mode;
586 }
587
588 static int run_command(struct command_context *context,
589 struct command *c, const char *words[], unsigned num_words)
590 {
591 if (!command_can_run(context, c)) {
592 /* Many commands may be run only before/after 'init' */
593 const char *when;
594 switch (c->mode) {
595 case COMMAND_CONFIG:
596 when = "before";
597 break;
598 case COMMAND_EXEC:
599 when = "after";
600 break;
601 /* handle the impossible with humor; it guarantees a bug report! */
602 default:
603 when = "if Cthulhu is summoned by";
604 break;
605 }
606 LOG_ERROR("The '%s' command must be used %s 'init'.",
607 c->name, when);
608 return ERROR_FAIL;
609 }
610
611 struct command_invocation cmd = {
612 .ctx = context,
613 .current = c,
614 .name = c->name,
615 .argc = num_words - 1,
616 .argv = words + 1,
617 };
618 int retval = c->handler(&cmd);
619 if (retval == ERROR_COMMAND_SYNTAX_ERROR) {
620 /* Print help for command */
621 char *full_name = command_name(c, ' ');
622 if (NULL != full_name) {
623 command_run_linef(context, "usage %s", full_name);
624 free(full_name);
625 } else
626 retval = -ENOMEM;
627 } else if (retval == ERROR_COMMAND_CLOSE_CONNECTION) {
628 /* just fall through for a shutdown request */
629 } else if (retval != ERROR_OK) {
630 /* we do not print out an error message because the command *should*
631 * have printed out an error
632 */
633 LOG_DEBUG("Command failed with error code %d", retval);
634 }
635
636 return retval;
637 }
638
639 int command_run_line(struct command_context *context, char *line)
640 {
641 /* all the parent commands have been registered with the interpreter
642 * so, can just evaluate the line as a script and check for
643 * results
644 */
645 /* run the line thru a script engine */
646 int retval = ERROR_FAIL;
647 int retcode;
648 /* Beware! This code needs to be reentrant. It is also possible
649 * for OpenOCD commands to be invoked directly from Tcl. This would
650 * happen when the Jim Tcl interpreter is provided by eCos for
651 * instance.
652 */
653 Jim_Interp *interp = context->interp;
654 Jim_DeleteAssocData(interp, "context");
655 retcode = Jim_SetAssocData(interp, "context", NULL, context);
656 if (retcode == JIM_OK) {
657 /* associated the return value */
658 Jim_DeleteAssocData(interp, "retval");
659 retcode = Jim_SetAssocData(interp, "retval", NULL, &retval);
660 if (retcode == JIM_OK) {
661 retcode = Jim_Eval_Named(interp, line, 0, 0);
662
663 Jim_DeleteAssocData(interp, "retval");
664 }
665 Jim_DeleteAssocData(interp, "context");
666 }
667 if (retcode == JIM_ERR) {
668 if (retval != ERROR_COMMAND_CLOSE_CONNECTION) {
669 /* We do not print the connection closed error message */
670 Jim_MakeErrorMessage(interp);
671 LOG_USER("%s", Jim_GetString(Jim_GetResult(interp), NULL));
672 }
673 if (retval == ERROR_OK) {
674 /* It wasn't a low level OpenOCD command that failed */
675 return ERROR_FAIL;
676 }
677 return retval;
678 } else if (retcode == JIM_EXIT) {
679 return ERROR_OK_EXIT;
680 } else {
681 const char *result;
682 int reslen;
683
684 result = Jim_GetString(Jim_GetResult(interp), &reslen);
685 if (reslen > 0) {
686 int i;
687 char buff[256 + 1];
688 for (i = 0; i < reslen; i += 256) {
689 int chunk;
690 chunk = reslen - i;
691 if (chunk > 256)
692 chunk = 256;
693 strncpy(buff, result + i, chunk);
694 buff[chunk] = 0;
695 LOG_USER_N("%s", buff);
696 }
697 LOG_USER_N("\n");
698 }
699 retval = ERROR_OK;
700 }
701 return retval;
702 }
703
704 int command_run_linef(struct command_context *context, const char *format, ...)
705 {
706 int retval = ERROR_FAIL;
707 char *string;
708 va_list ap;
709 va_start(ap, format);
710 string = alloc_vprintf(format, ap);
711 if (string != NULL) {
712 retval = command_run_line(context, string);
713 free(string);
714 }
715 va_end(ap);
716 return retval;
717 }
718
719 void command_set_output_handler(struct command_context *context,
720 command_output_handler_t output_handler, void *priv)
721 {
722 context->output_handler = output_handler;
723 context->output_handler_priv = priv;
724 }
725
726 struct command_context *copy_command_context(struct command_context *context)
727 {
728 struct command_context *copy_context = malloc(sizeof(struct command_context));
729
730 *copy_context = *context;
731
732 return copy_context;
733 }
734
735 void command_done(struct command_context *cmd_ctx)
736 {
737 if (NULL == cmd_ctx)
738 return;
739
740 free(cmd_ctx);
741 }
742
743 /* find full path to file */
744 static int jim_find(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
745 {
746 if (argc != 2)
747 return JIM_ERR;
748 const char *file = Jim_GetString(argv[1], NULL);
749 char *full_path = find_file(file);
750 if (full_path == NULL)
751 return JIM_ERR;
752 Jim_Obj *result = Jim_NewStringObj(interp, full_path, strlen(full_path));
753 free(full_path);
754
755 Jim_SetResult(interp, result);
756 return JIM_OK;
757 }
758
759 COMMAND_HANDLER(jim_echo)
760 {
761 if (CMD_ARGC == 2 && !strcmp(CMD_ARGV[0], "-n")) {
762 LOG_USER_N("%s", CMD_ARGV[1]);
763 return JIM_OK;
764 }
765 if (CMD_ARGC != 1)
766 return JIM_ERR;
767 LOG_USER("%s", CMD_ARGV[0]);
768 return JIM_OK;
769 }
770
771 /* Capture progress output and return as tcl return value. If the
772 * progress output was empty, return tcl return value.
773 */
774 static int jim_capture(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
775 {
776 if (argc != 2)
777 return JIM_ERR;
778
779 struct log_capture_state *state = command_log_capture_start(interp);
780
781 /* disable polling during capture. This avoids capturing output
782 * from polling.
783 *
784 * This is necessary in order to avoid accidentially getting a non-empty
785 * string for tcl fn's.
786 */
787 bool save_poll = jtag_poll_get_enabled();
788
789 jtag_poll_set_enabled(false);
790
791 const char *str = Jim_GetString(argv[1], NULL);
792 int retcode = Jim_Eval_Named(interp, str, __THIS__FILE__, __LINE__);
793
794 jtag_poll_set_enabled(save_poll);
795
796 command_log_capture_finish(state);
797
798 return retcode;
799 }
800
801 static COMMAND_HELPER(command_help_find, struct command *head,
802 struct command **out)
803 {
804 if (0 == CMD_ARGC)
805 return ERROR_COMMAND_SYNTAX_ERROR;
806 *out = command_find(head, CMD_ARGV[0]);
807 if (NULL == *out && strncmp(CMD_ARGV[0], "ocd_", 4) == 0)
808 *out = command_find(head, CMD_ARGV[0] + 4);
809 if (NULL == *out)
810 return ERROR_COMMAND_SYNTAX_ERROR;
811 if (--CMD_ARGC == 0)
812 return ERROR_OK;
813 CMD_ARGV++;
814 return CALL_COMMAND_HANDLER(command_help_find, (*out)->children, out);
815 }
816
817 static COMMAND_HELPER(command_help_show, struct command *c, unsigned n,
818 bool show_help, const char *match);
819
820 static COMMAND_HELPER(command_help_show_list, struct command *head, unsigned n,
821 bool show_help, const char *match)
822 {
823 for (struct command *c = head; NULL != c; c = c->next)
824 CALL_COMMAND_HANDLER(command_help_show, c, n, show_help, match);
825 return ERROR_OK;
826 }
827
828 #define HELP_LINE_WIDTH(_n) (int)(76 - (2 * _n))
829
830 static void command_help_show_indent(unsigned n)
831 {
832 for (unsigned i = 0; i < n; i++)
833 LOG_USER_N(" ");
834 }
835 static void command_help_show_wrap(const char *str, unsigned n, unsigned n2)
836 {
837 const char *cp = str, *last = str;
838 while (*cp) {
839 const char *next = last;
840 do {
841 cp = next;
842 do {
843 next++;
844 } while (*next != ' ' && *next != '\t' && *next != '\0');
845 } while ((next - last < HELP_LINE_WIDTH(n)) && *next != '\0');
846 if (next - last < HELP_LINE_WIDTH(n))
847 cp = next;
848 command_help_show_indent(n);
849 LOG_USER("%.*s", (int)(cp - last), last);
850 last = cp + 1;
851 n = n2;
852 }
853 }
854 static COMMAND_HELPER(command_help_show, struct command *c, unsigned n,
855 bool show_help, const char *match)
856 {
857 char *cmd_name = command_name(c, ' ');
858 if (NULL == cmd_name)
859 return -ENOMEM;
860
861 /* If the match string occurs anywhere, we print out
862 * stuff for this command. */
863 bool is_match = (strstr(cmd_name, match) != NULL) ||
864 ((c->usage != NULL) && (strstr(c->usage, match) != NULL)) ||
865 ((c->help != NULL) && (strstr(c->help, match) != NULL));
866
867 if (is_match) {
868 command_help_show_indent(n);
869 LOG_USER_N("%s", cmd_name);
870 }
871 free(cmd_name);
872
873 if (is_match) {
874 if (c->usage) {
875 LOG_USER_N(" ");
876 command_help_show_wrap(c->usage, 0, n + 5);
877 } else
878 LOG_USER_N("\n");
879 }
880
881 if (is_match && show_help) {
882 char *msg;
883
884 /* Normal commands are runtime-only; highlight exceptions */
885 if (c->mode != COMMAND_EXEC) {
886 const char *stage_msg = "";
887
888 switch (c->mode) {
889 case COMMAND_CONFIG:
890 stage_msg = " (configuration command)";
891 break;
892 case COMMAND_ANY:
893 stage_msg = " (command valid any time)";
894 break;
895 default:
896 stage_msg = " (?mode error?)";
897 break;
898 }
899 msg = alloc_printf("%s%s", c->help ? : "", stage_msg);
900 } else
901 msg = alloc_printf("%s", c->help ? : "");
902
903 if (NULL != msg) {
904 command_help_show_wrap(msg, n + 3, n + 3);
905 free(msg);
906 } else
907 return -ENOMEM;
908 }
909
910 if (++n > 5) {
911 LOG_ERROR("command recursion exceeded");
912 return ERROR_FAIL;
913 }
914
915 return CALL_COMMAND_HANDLER(command_help_show_list,
916 c->children, n, show_help, match);
917 }
918 COMMAND_HANDLER(handle_help_command)
919 {
920 bool full = strcmp(CMD_NAME, "help") == 0;
921 int retval;
922 struct command *c = CMD_CTX->commands;
923 char *match = NULL;
924
925 if (CMD_ARGC == 0)
926 match = "";
927 else if (CMD_ARGC >= 1) {
928 unsigned i;
929
930 for (i = 0; i < CMD_ARGC; ++i) {
931 if (NULL != match) {
932 char *prev = match;
933
934 match = alloc_printf("%s %s", match,
935 CMD_ARGV[i]);
936 free(prev);
937 if (NULL == match) {
938 LOG_ERROR("unable to build "
939 "search string");
940 return -ENOMEM;
941 }
942 } else {
943 match = alloc_printf("%s", CMD_ARGV[i]);
944 if (NULL == match) {
945 LOG_ERROR("unable to build "
946 "search string");
947 return -ENOMEM;
948 }
949 }
950 }
951 } else
952 return ERROR_COMMAND_SYNTAX_ERROR;
953
954 retval = CALL_COMMAND_HANDLER(command_help_show_list,
955 c, 0, full, match);
956
957 if (CMD_ARGC >= 1)
958 free(match);
959 return retval;
960 }
961
962 static int command_unknown_find(unsigned argc, Jim_Obj *const *argv,
963 struct command *head, struct command **out, bool top_level)
964 {
965 if (0 == argc)
966 return argc;
967 const char *cmd_name = Jim_GetString(argv[0], NULL);
968 struct command *c = command_find(head, cmd_name);
969 if (NULL == c && top_level && strncmp(cmd_name, "ocd_", 4) == 0)
970 c = command_find(head, cmd_name + 4);
971 if (NULL == c)
972 return argc;
973 *out = c;
974 return command_unknown_find(--argc, ++argv, (*out)->children, out, false);
975 }
976
977 static int command_unknown(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
978 {
979 const char *cmd_name = Jim_GetString(argv[0], NULL);
980 if (strcmp(cmd_name, "unknown") == 0) {
981 if (argc == 1)
982 return JIM_OK;
983 argc--;
984 argv++;
985 }
986 script_debug(interp, cmd_name, argc, argv);
987
988 struct command_context *cmd_ctx = current_command_context(interp);
989 struct command *c = cmd_ctx->commands;
990 int remaining = command_unknown_find(argc, argv, c, &c, true);
991 /* if nothing could be consumed, then it's really an unknown command */
992 if (remaining == argc) {
993 const char *cmd = Jim_GetString(argv[0], NULL);
994 LOG_ERROR("Unknown command:\n %s", cmd);
995 return JIM_OK;
996 }
997
998 bool found = true;
999 Jim_Obj *const *start;
1000 unsigned count;
1001 if (c->handler || c->jim_handler) {
1002 /* include the command name in the list */
1003 count = remaining + 1;
1004 start = argv + (argc - remaining - 1);
1005 } else {
1006 c = command_find(cmd_ctx->commands, "usage");
1007 if (NULL == c) {
1008 LOG_ERROR("unknown command, but usage is missing too");
1009 return JIM_ERR;
1010 }
1011 count = argc - remaining;
1012 start = argv;
1013 found = false;
1014 }
1015 /* pass the command through to the intended handler */
1016 if (c->jim_handler) {
1017 interp->cmdPrivData = c->jim_handler_data;
1018 return (*c->jim_handler)(interp, count, start);
1019 }
1020
1021 return script_command_run(interp, count, start, c, found);
1022 }
1023
1024 static int jim_command_mode(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1025 {
1026 struct command_context *cmd_ctx = current_command_context(interp);
1027 enum command_mode mode;
1028
1029 if (argc > 1) {
1030 struct command *c = cmd_ctx->commands;
1031 int remaining = command_unknown_find(argc - 1, argv + 1, c, &c, true);
1032 /* if nothing could be consumed, then it's an unknown command */
1033 if (remaining == argc - 1) {
1034 Jim_SetResultString(interp, "unknown", -1);
1035 return JIM_OK;
1036 }
1037 mode = c->mode;
1038 } else
1039 mode = cmd_ctx->mode;
1040
1041 const char *mode_str;
1042 switch (mode) {
1043 case COMMAND_ANY:
1044 mode_str = "any";
1045 break;
1046 case COMMAND_CONFIG:
1047 mode_str = "config";
1048 break;
1049 case COMMAND_EXEC:
1050 mode_str = "exec";
1051 break;
1052 default:
1053 mode_str = "unknown";
1054 break;
1055 }
1056 Jim_SetResultString(interp, mode_str, -1);
1057 return JIM_OK;
1058 }
1059
1060 static int jim_command_type(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
1061 {
1062 if (1 == argc)
1063 return JIM_ERR;
1064
1065 struct command_context *cmd_ctx = current_command_context(interp);
1066 struct command *c = cmd_ctx->commands;
1067 int remaining = command_unknown_find(argc - 1, argv + 1, c, &c, true);
1068 /* if nothing could be consumed, then it's an unknown command */
1069 if (remaining == argc - 1) {
1070 Jim_SetResultString(interp, "unknown", -1);
1071 return JIM_OK;
1072 }
1073
1074 if (c->jim_handler)
1075 Jim_SetResultString(interp, "native", -1);
1076 else if (c->handler)
1077 Jim_SetResultString(interp, "simple", -1);
1078 else
1079 Jim_SetResultString(interp, "group", -1);
1080
1081 return JIM_OK;
1082 }
1083
1084 int help_add_command(struct command_context *cmd_ctx, struct command *parent,
1085 const char *cmd_name, const char *help_text, const char *usage)
1086 {
1087 struct command **head = command_list_for_parent(cmd_ctx, parent);
1088 struct command *nc = command_find(*head, cmd_name);
1089 if (NULL == nc) {
1090 /* add a new command with help text */
1091 struct command_registration cr = {
1092 .name = cmd_name,
1093 .mode = COMMAND_ANY,
1094 .help = help_text,
1095 .usage = usage,
1096 };
1097 nc = register_command(cmd_ctx, parent, &cr);
1098 if (NULL == nc) {
1099 LOG_ERROR("failed to add '%s' help text", cmd_name);
1100 return ERROR_FAIL;
1101 }
1102 LOG_DEBUG("added '%s' help text", cmd_name);
1103 return ERROR_OK;
1104 }
1105 if (help_text) {
1106 bool replaced = false;
1107 if (nc->help) {
1108 free((void *)nc->help);
1109 replaced = true;
1110 }
1111 nc->help = strdup(help_text);
1112 if (replaced)
1113 LOG_INFO("replaced existing '%s' help", cmd_name);
1114 else
1115 LOG_DEBUG("added '%s' help text", cmd_name);
1116 }
1117 if (usage) {
1118 bool replaced = false;
1119 if (nc->usage) {
1120 free((void *)nc->usage);
1121 replaced = true;
1122 }
1123 nc->usage = strdup(usage);
1124 if (replaced)
1125 LOG_INFO("replaced existing '%s' usage", cmd_name);
1126 else
1127 LOG_DEBUG("added '%s' usage text", cmd_name);
1128 }
1129 return ERROR_OK;
1130 }
1131
1132 COMMAND_HANDLER(handle_help_add_command)
1133 {
1134 if (CMD_ARGC < 2) {
1135 LOG_ERROR("%s: insufficient arguments", CMD_NAME);
1136 return ERROR_COMMAND_SYNTAX_ERROR;
1137 }
1138
1139 /* save help text and remove it from argument list */
1140 const char *str = CMD_ARGV[--CMD_ARGC];
1141 const char *help = !strcmp(CMD_NAME, "add_help_text") ? str : NULL;
1142 const char *usage = !strcmp(CMD_NAME, "add_usage_text") ? str : NULL;
1143 if (!help && !usage) {
1144 LOG_ERROR("command name '%s' is unknown", CMD_NAME);
1145 return ERROR_COMMAND_SYNTAX_ERROR;
1146 }
1147 /* likewise for the leaf command name */
1148 const char *cmd_name = CMD_ARGV[--CMD_ARGC];
1149
1150 struct command *c = NULL;
1151 if (CMD_ARGC > 0) {
1152 c = CMD_CTX->commands;
1153 int retval = CALL_COMMAND_HANDLER(command_help_find, c, &c);
1154 if (ERROR_OK != retval)
1155 return retval;
1156 }
1157 return help_add_command(CMD_CTX, c, cmd_name, help, usage);
1158 }
1159
1160 /* sleep command sleeps for <n> milliseconds
1161 * this is useful in target startup scripts
1162 */
1163 COMMAND_HANDLER(handle_sleep_command)
1164 {
1165 bool busy = false;
1166 if (CMD_ARGC == 2) {
1167 if (strcmp(CMD_ARGV[1], "busy") == 0)
1168 busy = true;
1169 else
1170 return ERROR_COMMAND_SYNTAX_ERROR;
1171 } else if (CMD_ARGC < 1 || CMD_ARGC > 2)
1172 return ERROR_COMMAND_SYNTAX_ERROR;
1173
1174 unsigned long duration = 0;
1175 int retval = parse_ulong(CMD_ARGV[0], &duration);
1176 if (ERROR_OK != retval)
1177 return retval;
1178
1179 if (!busy) {
1180 long long then = timeval_ms();
1181 while (timeval_ms() - then < (long long)duration) {
1182 target_call_timer_callbacks_now();
1183 usleep(1000);
1184 }
1185 } else
1186 busy_sleep(duration);
1187
1188 return ERROR_OK;
1189 }
1190
1191 static const struct command_registration command_subcommand_handlers[] = {
1192 {
1193 .name = "mode",
1194 .mode = COMMAND_ANY,
1195 .jim_handler = jim_command_mode,
1196 .usage = "[command_name ...]",
1197 .help = "Returns the command modes allowed by a command:"
1198 "'any', 'config', or 'exec'. If no command is"
1199 "specified, returns the current command mode. "
1200 "Returns 'unknown' if an unknown command is given. "
1201 "Command can be multiple tokens.",
1202 },
1203 {
1204 .name = "type",
1205 .mode = COMMAND_ANY,
1206 .jim_handler = jim_command_type,
1207 .usage = "command_name [...]",
1208 .help = "Returns the type of built-in command:"
1209 "'native', 'simple', 'group', or 'unknown'. "
1210 "Command can be multiple tokens.",
1211 },
1212 COMMAND_REGISTRATION_DONE
1213 };
1214
1215 static const struct command_registration command_builtin_handlers[] = {
1216 {
1217 .name = "echo",
1218 .handler = jim_echo,
1219 .mode = COMMAND_ANY,
1220 .help = "Logs a message at \"user\" priority. "
1221 "Output message to stdout. "
1222 "Option \"-n\" suppresses trailing newline",
1223 .usage = "[-n] string",
1224 },
1225 {
1226 .name = "add_help_text",
1227 .handler = handle_help_add_command,
1228 .mode = COMMAND_ANY,
1229 .help = "Add new command help text; "
1230 "Command can be multiple tokens.",
1231 .usage = "command_name helptext_string",
1232 },
1233 {
1234 .name = "add_usage_text",
1235 .handler = handle_help_add_command,
1236 .mode = COMMAND_ANY,
1237 .help = "Add new command usage text; "
1238 "command can be multiple tokens.",
1239 .usage = "command_name usage_string",
1240 },
1241 {
1242 .name = "sleep",
1243 .handler = handle_sleep_command,
1244 .mode = COMMAND_ANY,
1245 .help = "Sleep for specified number of milliseconds. "
1246 "\"busy\" will busy wait instead (avoid this).",
1247 .usage = "milliseconds ['busy']",
1248 },
1249 {
1250 .name = "help",
1251 .handler = handle_help_command,
1252 .mode = COMMAND_ANY,
1253 .help = "Show full command help; "
1254 "command can be multiple tokens.",
1255 .usage = "[command_name]",
1256 },
1257 {
1258 .name = "usage",
1259 .handler = handle_help_command,
1260 .mode = COMMAND_ANY,
1261 .help = "Show basic command usage; "
1262 "command can be multiple tokens.",
1263 .usage = "[command_name]",
1264 },
1265 {
1266 .name = "command",
1267 .mode = COMMAND_ANY,
1268 .help = "core command group (introspection)",
1269 .chain = command_subcommand_handlers,
1270 },
1271 COMMAND_REGISTRATION_DONE
1272 };
1273
1274 struct command_context *command_init(const char *startup_tcl, Jim_Interp *interp)
1275 {
1276 struct command_context *context = malloc(sizeof(struct command_context));
1277 const char *HostOs;
1278
1279 context->mode = COMMAND_EXEC;
1280 context->commands = NULL;
1281 context->current_target = 0;
1282 context->output_handler = NULL;
1283 context->output_handler_priv = NULL;
1284
1285 /* Create a jim interpreter if we were not handed one */
1286 if (interp == NULL) {
1287 /* Create an interpreter */
1288 interp = Jim_CreateInterp();
1289 /* Add all the Jim core commands */
1290 Jim_RegisterCoreCommands(interp);
1291 Jim_InitStaticExtensions(interp);
1292 }
1293
1294 context->interp = interp;
1295
1296 /* Stick to lowercase for HostOS strings. */
1297 #if defined(_MSC_VER)
1298 /* WinXX - is generic, the forward
1299 * looking problem is this:
1300 *
1301 * "win32" or "win64"
1302 *
1303 * "winxx" is generic.
1304 */
1305 HostOs = "winxx";
1306 #elif defined(__linux__)
1307 HostOs = "linux";
1308 #elif defined(__APPLE__) || defined(__DARWIN__)
1309 HostOs = "darwin";
1310 #elif defined(__CYGWIN__)
1311 HostOs = "cygwin";
1312 #elif defined(__MINGW32__)
1313 HostOs = "mingw32";
1314 #elif defined(__ECOS)
1315 HostOs = "ecos";
1316 #elif defined(__FreeBSD__)
1317 HostOs = "freebsd";
1318 #elif defined(__OpenBSD__)
1319 HostOs = "openbsd";
1320 #else
1321 #warning "Unrecognized host OS..."
1322 HostOs = "other";
1323 #endif
1324 Jim_SetGlobalVariableStr(interp, "ocd_HOSTOS",
1325 Jim_NewStringObj(interp, HostOs, strlen(HostOs)));
1326
1327 Jim_CreateCommand(interp, "ocd_find", jim_find, NULL, NULL);
1328 Jim_CreateCommand(interp, "capture", jim_capture, NULL, NULL);
1329
1330 register_commands(context, NULL, command_builtin_handlers);
1331
1332 Jim_SetAssocData(interp, "context", NULL, context);
1333 if (Jim_Eval_Named(interp, startup_tcl, "embedded:startup.tcl", 1) == JIM_ERR) {
1334 LOG_ERROR("Failed to run startup.tcl (embedded into OpenOCD)");
1335 Jim_MakeErrorMessage(interp);
1336 LOG_USER_N("%s", Jim_GetString(Jim_GetResult(interp), NULL));
1337 exit(-1);
1338 }
1339 Jim_DeleteAssocData(interp, "context");
1340
1341 return context;
1342 }
1343
1344 int command_context_mode(struct command_context *cmd_ctx, enum command_mode mode)
1345 {
1346 if (!cmd_ctx)
1347 return ERROR_COMMAND_SYNTAX_ERROR;
1348
1349 cmd_ctx->mode = mode;
1350 return ERROR_OK;
1351 }
1352
1353 void process_jim_events(struct command_context *cmd_ctx)
1354 {
1355 static int recursion;
1356 if (recursion)
1357 return;
1358
1359 recursion++;
1360 Jim_ProcessEvents(cmd_ctx->interp, JIM_ALL_EVENTS | JIM_DONT_WAIT);
1361 recursion--;
1362 }
1363
1364 #define DEFINE_PARSE_NUM_TYPE(name, type, func, min, max) \
1365 int parse ## name(const char *str, type * ul) \
1366 { \
1367 if (!*str) { \
1368 LOG_ERROR("Invalid command argument"); \
1369 return ERROR_COMMAND_ARGUMENT_INVALID; \
1370 } \
1371 char *end; \
1372 *ul = func(str, &end, 0); \
1373 if (*end) { \
1374 LOG_ERROR("Invalid command argument"); \
1375 return ERROR_COMMAND_ARGUMENT_INVALID; \
1376 } \
1377 if ((max == *ul) && (ERANGE == errno)) { \
1378 LOG_ERROR("Argument overflow"); \
1379 return ERROR_COMMAND_ARGUMENT_OVERFLOW; \
1380 } \
1381 if (min && (min == *ul) && (ERANGE == errno)) { \
1382 LOG_ERROR("Argument underflow"); \
1383 return ERROR_COMMAND_ARGUMENT_UNDERFLOW; \
1384 } \
1385 return ERROR_OK; \
1386 }
1387 DEFINE_PARSE_NUM_TYPE(_ulong, unsigned long, strtoul, 0, ULONG_MAX)
1388 DEFINE_PARSE_NUM_TYPE(_ullong, unsigned long long, strtoull, 0, ULLONG_MAX)
1389 DEFINE_PARSE_NUM_TYPE(_long, long, strtol, LONG_MIN, LONG_MAX)
1390 DEFINE_PARSE_NUM_TYPE(_llong, long long, strtoll, LLONG_MIN, LLONG_MAX)
1391
1392 #define DEFINE_PARSE_WRAPPER(name, type, min, max, functype, funcname) \
1393 int parse ## name(const char *str, type * ul) \
1394 { \
1395 functype n; \
1396 int retval = parse ## funcname(str, &n); \
1397 if (ERROR_OK != retval) \
1398 return retval; \
1399 if (n > max) \
1400 return ERROR_COMMAND_ARGUMENT_OVERFLOW; \
1401 if (min) \
1402 return ERROR_COMMAND_ARGUMENT_UNDERFLOW; \
1403 *ul = n; \
1404 return ERROR_OK; \
1405 }
1406
1407 #define DEFINE_PARSE_ULONG(name, type, min, max) \
1408 DEFINE_PARSE_WRAPPER(name, type, min, max, unsigned long, _ulong)
1409 DEFINE_PARSE_ULONG(_uint, unsigned, 0, UINT_MAX)
1410 DEFINE_PARSE_ULONG(_u32, uint32_t, 0, UINT32_MAX)
1411 DEFINE_PARSE_ULONG(_u16, uint16_t, 0, UINT16_MAX)
1412 DEFINE_PARSE_ULONG(_u8, uint8_t, 0, UINT8_MAX)
1413
1414 #define DEFINE_PARSE_LONG(name, type, min, max) \
1415 DEFINE_PARSE_WRAPPER(name, type, min, max, long, _long)
1416 DEFINE_PARSE_LONG(_int, int, n < INT_MIN, INT_MAX)
1417 DEFINE_PARSE_LONG(_s32, int32_t, n < INT32_MIN, INT32_MAX)
1418 DEFINE_PARSE_LONG(_s16, int16_t, n < INT16_MIN, INT16_MAX)
1419 DEFINE_PARSE_LONG(_s8, int8_t, n < INT8_MIN, INT8_MAX)
1420
1421 static int command_parse_bool(const char *in, bool *out,
1422 const char *on, const char *off)
1423 {
1424 if (strcasecmp(in, on) == 0)
1425 *out = true;
1426 else if (strcasecmp(in, off) == 0)
1427 *out = false;
1428 else
1429 return ERROR_COMMAND_SYNTAX_ERROR;
1430 return ERROR_OK;
1431 }
1432
1433 int command_parse_bool_arg(const char *in, bool *out)
1434 {
1435 if (command_parse_bool(in, out, "on", "off") == ERROR_OK)
1436 return ERROR_OK;
1437 if (command_parse_bool(in, out, "enable", "disable") == ERROR_OK)
1438 return ERROR_OK;
1439 if (command_parse_bool(in, out, "true", "false") == ERROR_OK)
1440 return ERROR_OK;
1441 if (command_parse_bool(in, out, "yes", "no") == ERROR_OK)
1442 return ERROR_OK;
1443 if (command_parse_bool(in, out, "1", "0") == ERROR_OK)
1444 return ERROR_OK;
1445 return ERROR_COMMAND_SYNTAX_ERROR;
1446 }
1447
1448 COMMAND_HELPER(handle_command_parse_bool, bool *out, const char *label)
1449 {
1450 switch (CMD_ARGC) {
1451 case 1: {
1452 const char *in = CMD_ARGV[0];
1453 if (command_parse_bool_arg(in, out) != ERROR_OK) {
1454 LOG_ERROR("%s: argument '%s' is not valid", CMD_NAME, in);
1455 return ERROR_COMMAND_SYNTAX_ERROR;
1456 }
1457 /* fall through */
1458 }
1459 case 0:
1460 LOG_INFO("%s is %s", label, *out ? "enabled" : "disabled");
1461 break;
1462 default:
1463 return ERROR_COMMAND_SYNTAX_ERROR;
1464 }
1465 return ERROR_OK;
1466 }

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)