]> git.sur5r.net Git - openocd/blob - src/helper/command.c
OpenOCD commands w/prefix ocd_ now set the primary Tcl return value instead of messin...
[openocd] / src / helper / command.c
1 /***************************************************************************
2  *   Copyright (C) 2005 by Dominic Rath                                    *
3  *   Dominic.Rath@gmx.de                                                   *
4  *                                                                         *
5  *   part of this file is taken from libcli (libcli.sourceforge.net)       *
6  *   Copyright (C) David Parrish (david@dparrish.com)                      *
7  *                                                                         *
8  *   This program is free software; you can redistribute it and/or modify  *
9  *   it under the terms of the GNU General Public License as published by  *
10  *   the Free Software Foundation; either version 2 of the License, or     *
11  *   (at your option) any later version.                                   *
12  *                                                                         *
13  *   This program is distributed in the hope that it will be useful,       *
14  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
15  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
16  *   GNU General Public License for more details.                          *
17  *                                                                         *
18  *   You should have received a copy of the GNU General Public License     *
19  *   along with this program; if not, write to the                         *
20  *   Free Software Foundation, Inc.,                                       *
21  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
22  ***************************************************************************/
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26
27 #include "replacements.h"
28 #include "target.h"
29 #include "command.h"
30 #include "configuration.h"
31
32 #include "log.h"
33 #include "time_support.h"
34
35 #include <stdlib.h>
36 #include <string.h>
37 #include <ctype.h>
38 #include <stdarg.h>
39 #include <stdio.h>
40 #include <unistd.h>
41 #include <errno.h>
42
43 int fast_and_dangerous = 0;
44 Jim_Interp *interp = NULL;
45
46 int handle_sleep_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc);
47 int handle_fast_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc);
48
49 int run_command(command_context_t *context, command_t *c, char *words[], int num_words);
50
51 static void tcl_output(void *privData, const char *file, int line, const char *function, const char *string)
52 {               
53         Jim_Obj *tclOutput=(Jim_Obj *)privData;
54
55         Jim_AppendString(interp, tclOutput, string, strlen(string));
56 }
57
58 extern command_context_t *global_cmd_ctx;
59
60
61 static int script_command(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
62 {
63         /* the private data is stashed in the interp structure */
64         command_t *c;
65         command_context_t *context;
66         int retval;
67         int i;
68         int nwords;
69         char **words;
70
71         target_call_timer_callbacks_now();
72         LOG_USER_N("%s", ""); /* Keep GDB connection alive*/ 
73         
74         c = interp->cmdPrivData;
75         LOG_DEBUG("script_command - %s", c->name);
76
77         words = malloc(sizeof(char *) * argc);
78         for (i = 0; i < argc; i++)
79         {
80                 int len;
81                 const char *w=Jim_GetString(argv[i], &len);
82                 if (*w=='#')
83                 {
84                         /* hit an end of line comment */
85                         break;
86                 }
87                 words[i] = strdup(w);
88                 if (words[i] == NULL) 
89                 {
90                         return JIM_ERR;
91                 }
92                 LOG_DEBUG("script_command - %s, argv[%u]=%s", c->name, i, words[i]);
93         }
94         nwords = i;
95
96         /* grab the command context from the associated data */
97         context = Jim_GetAssocData(interp, "context");
98         if (context == NULL)
99         {
100                 /* Tcl can invoke commands directly instead of via command_run_line(). This would
101                  * happen when the Jim Tcl interpreter is provided by eCos.
102                  */
103                 context = global_cmd_ctx;
104         }
105         
106         /* capture log output and return it */
107         Jim_Obj *tclOutput = Jim_NewStringObj(interp, "", 0);
108         /* a garbage collect can happen, so we need a reference count to this object */
109         Jim_IncrRefCount(tclOutput);
110         
111         log_add_callback(tcl_output, tclOutput);
112         
113         retval = run_command(context, c, words, nwords);
114         
115         log_remove_callback(tcl_output, tclOutput);
116
117         /* We dump output into this local variable */
118         Jim_SetResult(interp, tclOutput);
119         Jim_DecrRefCount(interp, tclOutput);
120
121         for (i = 0; i < nwords; i++)
122                 free(words[i]);
123         free(words);
124
125         int *return_retval = Jim_GetAssocData(interp, "retval");
126         if (return_retval != NULL)
127         {
128                 *return_retval = retval;
129         }
130         
131         return (retval==ERROR_OK)?JIM_OK:JIM_ERR;
132 }
133
134 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)
135 {
136         command_t *c, *p;
137         
138         if (!context || !name)
139                 return NULL;
140                                 
141         c = malloc(sizeof(command_t));
142         
143         c->name = strdup(name);
144         c->parent = parent;
145         c->children = NULL;
146         c->handler = handler;
147         c->mode = mode;
148         if (!help)
149                 help="";
150         c->next = NULL;
151         
152         /* place command in tree */
153         if (parent)
154         {
155                 if (parent->children)
156                 {
157                         /* find last child */
158                         for (p = parent->children; p && p->next; p = p->next);
159                         if (p)
160                                 p->next = c;
161                 }
162                 else
163                 {
164                         parent->children = c;
165                 }
166         }
167         else
168         {
169                 if (context->commands)
170                 {
171                         /* find last command */
172                         for (p = context->commands; p && p->next; p = p->next);
173                         if (p)
174                                 p->next = c;
175                 }
176                 else
177                 {
178                         context->commands = c;
179                 }
180         }
181         
182         /* just a placeholder, no handler */
183         if (c->handler==NULL)
184                 return c;
185
186         /* If this is a two level command, e.g. "flash banks", then the
187          * "unknown" proc in startup.tcl must redirect to  this command.
188          * 
189          * "flash banks" is translated by "unknown" to "flash_banks"
190          * if such a proc exists
191          */
192         /* Print help for command */
193         const char *t1="";
194         const char *t2="";
195         const char *t3="";
196         /* maximum of two levels :-) */
197         if (c->parent!=NULL)
198         {
199                 t1=c->parent->name;
200                 t2="_";
201         }
202         t3=c->name;
203         const char *full_name=alloc_printf("ocd_%s%s%s", t1, t2, t3);
204         Jim_CreateCommand(interp, full_name, script_command, c, NULL);
205         free((void *)full_name);
206         
207         /* we now need to add an overrideable proc */
208         const char *override_name=alloc_printf("proc %s%s%s {args} {eval \"ocd_%s%s%s $args\";return \"\"}", t1, t2, t3, t1, t2, t3);
209         Jim_Eval(interp, override_name);        
210         free((void *)override_name);
211         
212         /* accumulate help text in Tcl helptext list.  */
213     Jim_Obj *helptext=Jim_GetGlobalVariableStr(interp, "ocd_helptext", JIM_ERRMSG);
214     if (Jim_IsShared(helptext))
215         helptext = Jim_DuplicateObj(interp, helptext);
216         Jim_Obj *cmd_entry=Jim_NewListObj(interp, NULL, 0);
217         
218         Jim_Obj *cmd_list=Jim_NewListObj(interp, NULL, 0);
219
220         /* maximum of two levels :-) */
221         if (c->parent!=NULL)
222         {
223                 Jim_ListAppendElement(interp, cmd_list, Jim_NewStringObj(interp, c->parent->name, -1));
224         } 
225         Jim_ListAppendElement(interp, cmd_list, Jim_NewStringObj(interp, c->name, -1));
226         
227         Jim_ListAppendElement(interp, cmd_entry, cmd_list);
228         Jim_ListAppendElement(interp, cmd_entry, Jim_NewStringObj(interp, help, -1));
229         Jim_ListAppendElement(interp, helptext, cmd_entry);
230         return c;
231 }
232
233 int unregister_all_commands(command_context_t *context)
234 {
235         command_t *c, *c2;
236         
237         if (context == NULL)
238                 return ERROR_OK;
239         
240         while(NULL != context->commands)
241         {
242                 c = context->commands;
243                 
244                 while(NULL != c->children)
245                 {
246                         c2 = c->children;
247                         c->children = c->children->next;
248                         free(c2->name);
249                         c2->name = NULL;
250                         free(c2);
251                         c2 = NULL;
252                 }
253                 
254                 context->commands = context->commands->next;
255                 
256                 free(c->name);
257                 c->name = NULL;
258                 free(c);
259                 c = NULL;               
260         }
261         
262         return ERROR_OK;
263 }
264
265 int unregister_command(command_context_t *context, char *name)
266 {
267         command_t *c, *p = NULL, *c2;
268         
269         if ((!context) || (!name))
270                 return ERROR_INVALID_ARGUMENTS;
271         
272         /* find command */
273         for (c = context->commands; c; c = c->next)
274         {
275                 if (strcmp(name, c->name) == 0)
276                 {
277                         /* unlink command */
278                         if (p)
279                         {
280                                 p->next = c->next;
281                         }
282                         else
283                         {
284                                 context->commands = c->next;
285                         }
286                         
287                         /* unregister children */
288                         if (c->children)
289                         {
290                                 for (c2 = c->children; c2; c2 = c2->next)
291                                 {
292                                         free(c2->name);
293                                         free(c2);
294                                 }
295                         }
296                         
297                         /* delete command */
298                         free(c->name);
299                         free(c);
300                 }
301                 
302                 /* remember the last command for unlinking */
303                 p = c;
304         }
305         
306         return ERROR_OK;
307 }
308
309 void command_output_text(command_context_t *context, const char *data)
310 {
311         if( context && context->output_handler && data  ){
312                 context->output_handler( context, data );
313         }
314 }
315
316 void command_print_n(command_context_t *context, char *format, ...)
317 {
318         char *string;
319         
320         va_list ap;
321         va_start(ap, format);
322
323         string = alloc_vprintf(format, ap);
324         if (string != NULL)
325         {
326                 /* we want this collected in the log + we also want to pick it up as a tcl return
327                  * value.
328                  * 
329                  * The latter bit isn't precisely neat, but will do for now.
330                  */
331                 LOG_USER_N("%s", string);
332                 // We already printed it above
333                 //command_output_text(context, string);
334                 free(string);
335         }
336
337         va_end(ap);
338 }
339
340 void command_print(command_context_t *context, char *format, ...)
341 {
342         char *string;
343
344         va_list ap;
345         va_start(ap, format);
346
347         string = alloc_vprintf(format, ap);
348         if (string != NULL)
349         {
350                 strcat(string, "\n"); /* alloc_vprintf guaranteed the buffer to be at least one char longer */
351                 /* we want this collected in the log + we also want to pick it up as a tcl return
352                  * value.
353                  * 
354                  * The latter bit isn't precisely neat, but will do for now.
355                  */
356                 LOG_USER_N("%s", string);
357                 // We already printed it above
358                 //command_output_text(context, string);
359                 free(string);
360         }
361
362         va_end(ap);
363 }
364
365 int run_command(command_context_t *context, command_t *c, char *words[], int num_words)
366 {
367         int start_word=0;
368         if (!((context->mode == COMMAND_CONFIG) || (c->mode == COMMAND_ANY) || (c->mode == context->mode) ))
369         {
370                 /* Config commands can not run after the config stage */
371                 LOG_ERROR("Illegal mode for command");
372                 return ERROR_FAIL;
373         }
374         
375         int retval = c->handler(context, c->name, words + start_word + 1, num_words - start_word - 1);
376         if (retval == ERROR_COMMAND_SYNTAX_ERROR)
377         {
378                 /* Print help for command */
379                 const char *t1="";
380                 const char *t2="";
381                 const char *t3="";
382                 /* maximum of two levels :-) */
383                 if (c->parent!=NULL)
384                 {
385                         t1=c->parent->name;
386                         t2=" ";
387                 }
388                 t3=c->name;
389                 command_run_linef(context, "help {%s%s%s}", t1, t2, t3);
390         }
391         else if (retval == ERROR_COMMAND_CLOSE_CONNECTION)
392         {
393                 /* just fall through for a shutdown request */
394         }
395         else if (retval != ERROR_OK)
396         {
397                 /* we do not print out an error message because the command *should*
398                  * have printed out an error
399                  */
400                 LOG_DEBUG("Command failed with error code %d", retval); 
401         }
402         
403         return retval; 
404 }
405
406 int command_run_line(command_context_t *context, char *line)
407 {
408         /* all the parent commands have been registered with the interpreter
409          * so, can just evaluate the line as a script and check for
410          * results
411          */
412         /* run the line thru a script engine */
413         int retval=ERROR_FAIL;
414         int retcode;
415         /* Beware! This code needs to be reentrant. It is also possible
416          * for OpenOCD commands to be invoked directly from Tcl. This would
417          * happen when the Jim Tcl interpreter is provided by eCos for
418          * instance.
419          */
420         Jim_DeleteAssocData(interp, "context");
421         retcode = Jim_SetAssocData(interp, "context", NULL, context);
422         if (retcode == JIM_OK)
423         {
424                 /* associated the return value */
425                 Jim_DeleteAssocData(interp, "retval");
426                 retcode = Jim_SetAssocData(interp, "retval", NULL, &retval);
427                 if (retcode == JIM_OK)
428                 {
429                         retcode = Jim_Eval(interp, line);
430                         
431                         Jim_DeleteAssocData(interp, "retval");
432                 }       
433                 Jim_DeleteAssocData(interp, "context");
434         }
435         if (retcode == JIM_ERR) {
436                 if (retval!=ERROR_COMMAND_CLOSE_CONNECTION)
437                 {
438                         /* We do not print the connection closed error message */
439                         Jim_PrintErrorMessage(interp);
440                 }
441                 if (retval==ERROR_OK)
442                 {
443                         /* It wasn't a low level OpenOCD command that failed */
444                         return ERROR_FAIL; 
445                 }
446                 return retval;
447         } else if (retcode == JIM_EXIT) {
448                 /* ignore. */
449                 /* exit(Jim_GetExitCode(interp)); */
450         } else {
451                 const char *result;
452                 int reslen;
453
454                 result = Jim_GetString(Jim_GetResult(interp), &reslen);
455                 if (reslen) {
456                         int i;
457                         char buff[256+1];
458                         for (i = 0; i < reslen; i += 256)
459                         {
460                                 int chunk;
461                                 chunk = reslen - i;
462                                 if (chunk > 256)
463                                         chunk = 256;
464                                 strncpy(buff, result+i, chunk);
465                                 buff[chunk] = 0; 
466                                 LOG_USER_N("%s", buff);
467                         }
468                         LOG_USER_N("%s", "\n");
469                 }
470         }
471         return retval;
472 }
473
474 int command_run_linef(command_context_t *context, char *format, ...)
475 {
476         int retval=ERROR_FAIL;
477         char *string;
478         va_list ap;
479         va_start(ap, format);
480         string = alloc_vprintf(format, ap);
481         if (string!=NULL)
482         {
483                 retval=command_run_line(context, string);
484         }
485         va_end(ap);
486         return retval;
487 }
488
489 void command_set_output_handler(command_context_t* context, int (*output_handler)(struct command_context_s *context, const char* line), void *priv)
490 {
491         context->output_handler = output_handler;
492         context->output_handler_priv = priv;
493 }
494
495 command_context_t* copy_command_context(command_context_t* context)
496 {
497         command_context_t* copy_context = malloc(sizeof(command_context_t));
498
499         *copy_context = *context;
500
501         return copy_context;
502 }
503
504 int command_done(command_context_t *context)
505 {
506         free(context);
507         context = NULL;
508         
509         return ERROR_OK;
510 }
511
512 /* find full path to file */
513 static int jim_find(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
514 {
515         if (argc != 2)
516                 return JIM_ERR;
517         const char *file = Jim_GetString(argv[1], NULL);
518         char *full_path = find_file(file);
519         if (full_path == NULL)
520                 return JIM_ERR;
521         Jim_Obj *result = Jim_NewStringObj(interp, full_path, strlen(full_path));
522         free(full_path);
523         
524         Jim_SetResult(interp, result);
525         return JIM_OK;
526 }
527
528 static int jim_echo(Jim_Interp *interp, int argc, Jim_Obj *const *argv)
529 {
530         if (argc != 2)
531                 return JIM_ERR;
532         const char *str = Jim_GetString(argv[1], NULL);
533         LOG_USER("%s", str);
534         return JIM_OK;
535 }
536
537 static size_t openocd_jim_fwrite(const void *_ptr, size_t size, size_t n, void *cookie)
538 {
539         size_t nbytes;
540         const char *ptr;
541         Jim_Interp *interp;
542
543         /* make it a char easier to read code */
544         ptr = _ptr;
545         interp = cookie;
546         nbytes = size * n;
547         if (ptr == NULL || interp == NULL || nbytes == 0) {
548                 return 0;
549         }
550
551         /* do we have to chunk it? */
552         if (ptr[nbytes] == 0)
553         {
554                 /* no it is a C style string */
555                 LOG_USER_N("%s", ptr);
556                 return strlen(ptr);
557         }
558         /* GRR we must chunk - not null terminated */
559         while (nbytes) {
560                 char chunk[128+1];
561                 int x;
562
563                 x = nbytes;
564                 if (x > 128) {
565                         x = 128;
566                 }
567                 /* copy it */
568                 memcpy(chunk, ptr, x);
569                 /* terminate it */
570                 chunk[n] = 0;
571                 /* output it */
572                 LOG_USER_N("%s", chunk);
573                 ptr += x;
574                 nbytes -= x;
575         }
576         
577         return n;
578 }
579
580 static size_t openocd_jim_fread(void *ptr, size_t size, size_t n, void *cookie)
581 {
582         /* TCL wants to read... tell him no */
583         return 0;
584 }
585
586 static int openocd_jim_vfprintf(void *cookie, const char *fmt, va_list ap)
587 {
588         char *cp;
589         int n;
590         Jim_Interp *interp;
591
592         n = -1;
593         interp = cookie;
594         if (interp == NULL)
595                 return n;
596
597         cp = alloc_vprintf(fmt, ap);
598         if (cp)
599         {
600                 LOG_USER_N("%s", cp);
601                 n = strlen(cp);
602                 free(cp);
603         }
604         return n;
605 }
606
607 static int openocd_jim_fflush(void *cookie)
608 {
609         /* nothing to flush */
610         return 0;
611 }
612
613 static char* openocd_jim_fgets(char *s, int size, void *cookie)
614 {
615         /* not supported */
616         errno = ENOTSUP;
617         return NULL;
618 }
619
620 command_context_t* command_init()
621 {
622         command_context_t* context = malloc(sizeof(command_context_t));
623         extern unsigned const char startup_tcl[];
624
625         context->mode = COMMAND_EXEC;
626         context->commands = NULL;
627         context->current_target = 0;
628         context->output_handler = NULL;
629         context->output_handler_priv = NULL;
630
631 #ifdef JIM_EMBEDDED
632         Jim_InitEmbedded();
633         /* Create an interpreter */
634         interp = Jim_CreateInterp();
635         /* Add all the Jim core commands */
636         Jim_RegisterCoreCommands(interp);
637 #endif
638
639         Jim_CreateCommand(interp, "ocd_find", jim_find, NULL, NULL);
640         Jim_CreateCommand(interp, "echo", jim_echo, NULL, NULL);
641
642         /* Set Jim's STDIO */
643         interp->cookie_stdin = interp;
644         interp->cookie_stdout = interp;
645         interp->cookie_stderr = interp;
646         interp->cb_fwrite = openocd_jim_fwrite;
647         interp->cb_fread = openocd_jim_fread ;
648         interp->cb_vfprintf = openocd_jim_vfprintf;
649         interp->cb_fflush = openocd_jim_fflush;
650         interp->cb_fgets = openocd_jim_fgets;
651         
652         add_default_dirs();
653
654         if (Jim_Eval(interp, startup_tcl)==JIM_ERR)
655         {
656                 LOG_ERROR("Failed to run startup.tcl (embedded into OpenOCD compile time)");
657                 Jim_PrintErrorMessage(interp);
658                 exit(-1);
659         }
660
661         register_command(context, NULL, "sleep", handle_sleep_command,
662                                          COMMAND_ANY, "sleep for <n> milliseconds");
663         
664         register_command(context, NULL, "fast", handle_fast_command,
665                                          COMMAND_ANY, "fast <enable/disable> - place at beginning of config files. Sets defaults to fast and dangerous.");
666         
667         return context;
668 }
669
670 int command_context_mode(command_context_t *cmd_ctx, enum command_mode mode)
671 {
672         if (!cmd_ctx)
673                 return ERROR_INVALID_ARGUMENTS;
674
675         cmd_ctx->mode = mode;
676         return ERROR_OK;
677 }
678
679 /* sleep command sleeps for <n> miliseconds
680  * this is useful in target startup scripts
681  */
682 int handle_sleep_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
683 {
684         unsigned long duration = 0;
685         
686         if (argc == 1)
687         {
688                 duration = strtoul(args[0], NULL, 0);
689                 usleep(duration * 1000);
690         }
691
692         return ERROR_OK;
693 }
694
695 int handle_fast_command(struct command_context_s *cmd_ctx, char *cmd, char **args, int argc)
696 {
697         if (argc!=1)
698                 return ERROR_COMMAND_SYNTAX_ERROR;
699         
700         fast_and_dangerous = strcmp("enable", args[0])==0;
701         
702         return ERROR_OK;
703 }
704
705 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)
706 {
707         Jim_CreateCommand(interp, name, cmd, NULL, NULL);
708
709         /* FIX!!! it would be prettier to invoke add_help_text... 
710            accumulate help text in Tcl helptext list.  */
711         Jim_Obj *helptext=Jim_GetGlobalVariableStr(interp, "ocd_helptext", JIM_ERRMSG);
712         if (Jim_IsShared(helptext))
713                 helptext = Jim_DuplicateObj(interp, helptext);
714     
715         Jim_Obj *cmd_entry=Jim_NewListObj(interp, NULL, 0);
716         
717         Jim_Obj *cmd_list=Jim_NewListObj(interp, NULL, 0);
718         Jim_ListAppendElement(interp, cmd_list, Jim_NewStringObj(interp, name, -1));
719         
720         Jim_ListAppendElement(interp, cmd_entry, cmd_list);
721         Jim_ListAppendElement(interp, cmd_entry, Jim_NewStringObj(interp, help, -1));
722         Jim_ListAppendElement(interp, helptext, cmd_entry);
723 }