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