1 // Copyright 2016 The Fuchsia Authors
2 // Copyright (c) 2008-2009 Travis Geiselbrecht
3 //
4 // Use of this source code is governed by a MIT-style
5 // license that can be found in the LICENSE file or at
6 // https://opensource.org/licenses/MIT
7 
8 #include <lib/console.h>
9 
10 #include <assert.h>
11 #include <ctype.h>
12 #include <debug.h>
13 #include <err.h>
14 #include <kernel/cmdline.h>
15 #include <kernel/mutex.h>
16 #include <kernel/thread.h>
17 #include <lk/init.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 #include <trace.h>
22 #include <zircon/compiler.h>
23 #include <zircon/types.h>
24 
25 #ifndef CONSOLE_ENABLE_HISTORY
26 #define CONSOLE_ENABLE_HISTORY 1
27 #endif
28 
29 #define LINE_LEN 128
30 
31 #define PANIC_LINE_LEN 32
32 
33 #define MAX_NUM_ARGS 16
34 
35 #define HISTORY_LEN 16
36 
37 #define LOCAL_TRACE 0
38 
39 #define WHITESPACE " \t"
40 
41 /* debug buffer */
42 static char* debug_buffer;
43 
44 /* echo commands? */
45 static bool echo = true;
46 
47 /* command processor state */
48 static mutex_t command_lock = MUTEX_INITIAL_VALUE(command_lock);
49 int lastresult;
50 static bool abort_script;
51 
52 #if CONSOLE_ENABLE_HISTORY
53 /* command history stuff */
54 static char* history; // HISTORY_LEN rows of LINE_LEN chars a piece
55 static uint history_next;
56 
57 static void init_history(void);
58 static void add_history(const char* line);
59 static uint start_history_cursor(void);
60 static const char* next_history(uint* cursor);
61 static const char* prev_history(uint* cursor);
62 static void dump_history(void);
63 #endif
64 
65 // A linear array of statically defined commands.
66 extern const cmd __start_commands[];
67 extern const cmd __stop_commands[];
68 
69 static int cmd_help(int argc, const cmd_args* argv, uint32_t flags);
70 static int cmd_echo(int argc, const cmd_args* argv, uint32_t flags);
71 static int cmd_test(int argc, const cmd_args* argv, uint32_t flags);
72 #if CONSOLE_ENABLE_HISTORY
73 static int cmd_history(int argc, const cmd_args* argv, uint32_t flags);
74 #endif
75 
76 STATIC_COMMAND_START
77 STATIC_COMMAND_MASKED("help", "this list", &cmd_help, CMD_AVAIL_ALWAYS)
78 STATIC_COMMAND_MASKED("echo", NULL, &cmd_echo, CMD_AVAIL_ALWAYS)
79 #if LK_DEBUGLEVEL > 1
80 STATIC_COMMAND_MASKED("test", "test the command processor", &cmd_test, CMD_AVAIL_ALWAYS)
81 #if CONSOLE_ENABLE_HISTORY
82 STATIC_COMMAND_MASKED("history", "command history", &cmd_history, CMD_AVAIL_ALWAYS)
83 #endif
84 #endif
85 STATIC_COMMAND_END(help);
86 
console_init(uint level)87 static void console_init(uint level) {
88 #if CONSOLE_ENABLE_HISTORY
89     init_history();
90 #endif
91 }
92 
93 LK_INIT_HOOK(console, console_init, LK_INIT_LEVEL_HEAP);
94 
95 #if CONSOLE_ENABLE_HISTORY
cmd_history(int argc,const cmd_args * argv,uint32_t flags)96 static int cmd_history(int argc, const cmd_args* argv, uint32_t flags) {
97     dump_history();
98     return 0;
99 }
100 
history_line(uint line)101 static inline char* history_line(uint line) {
102     return history + line * LINE_LEN;
103 }
104 
ptrnext(uint ptr)105 static inline uint ptrnext(uint ptr) {
106     return (ptr + 1) % HISTORY_LEN;
107 }
108 
ptrprev(uint ptr)109 static inline uint ptrprev(uint ptr) {
110     return (ptr - 1) % HISTORY_LEN;
111 }
112 
dump_history(void)113 static void dump_history(void) {
114     printf("command history:\n");
115     uint ptr = ptrprev(history_next);
116     int i;
117     for (i = 0; i < HISTORY_LEN; i++) {
118         if (history_line(ptr)[0] != 0)
119             printf("\t%s\n", history_line(ptr));
120         ptr = ptrprev(ptr);
121     }
122 }
123 
init_history(void)124 static void init_history(void) {
125     /* allocate and set up the history buffer */
126     history = static_cast<char*>(calloc(1, HISTORY_LEN * LINE_LEN));
127     history_next = 0;
128 }
129 
add_history(const char * line)130 static void add_history(const char* line) {
131     // reject some stuff
132     if (line[0] == 0)
133         return;
134 
135     uint last = ptrprev(history_next);
136     if (strcmp(line, history_line(last)) == 0)
137         return;
138 
139     strlcpy(history_line(history_next), line, LINE_LEN);
140     history_next = ptrnext(history_next);
141 }
142 
start_history_cursor(void)143 static uint start_history_cursor(void) {
144     return ptrprev(history_next);
145 }
146 
next_history(uint * cursor)147 static const char* next_history(uint* cursor) {
148     uint i = ptrnext(*cursor);
149 
150     if (i == history_next)
151         return ""; // can't let the cursor hit the head
152 
153     *cursor = i;
154     return history_line(i);
155 }
156 
prev_history(uint * cursor)157 static const char* prev_history(uint* cursor) {
158     uint i;
159     const char* str = history_line(*cursor);
160 
161     /* if we are already at head, stop here */
162     if (*cursor == history_next)
163         return str;
164 
165     /* back up one */
166     i = ptrprev(*cursor);
167 
168     /* if the next one is gonna be null */
169     if (history_line(i)[0] == '\0')
170         return str;
171 
172     /* update the cursor */
173     *cursor = i;
174     return str;
175 }
176 #endif
177 
match_command(const char * command,const uint8_t availability_mask)178 static const cmd* match_command(const char* command, const uint8_t availability_mask) {
179     for (const cmd* curr_cmd = __start_commands;
180          curr_cmd != __stop_commands;
181          ++curr_cmd) {
182         if ((availability_mask & curr_cmd->availability_mask) != 0 &&
183             strcmp(command, curr_cmd->cmd_str) == 0) {
184             return curr_cmd;
185         }
186     }
187     return NULL;
188 }
189 
cgetchar(void)190 static inline int cgetchar(void) {
191     char c;
192     int r = platform_dgetc(&c, true);
193     return (r < 0) ? r : c;
194 }
cputchar(char c)195 static inline void cputchar(char c) {
196     platform_dputc(c);
197 }
cputs(const char * s)198 static inline void cputs(const char* s) {
199     platform_dputs_thread(s, strlen(s));
200 }
201 
read_debug_line(const char ** outbuffer,void * cookie)202 static int read_debug_line(const char** outbuffer, void* cookie) {
203     size_t pos = 0;
204     int escape_level = 0;
205 #if CONSOLE_ENABLE_HISTORY
206     uint history_cursor = start_history_cursor();
207 #endif
208 
209     char* buffer = debug_buffer;
210 
211     for (;;) {
212         /* loop until we get a char */
213         int ci;
214         if ((ci = cgetchar()) < 0)
215             continue;
216 
217         char c = static_cast<char>(ci);
218 
219         //      TRACEF("c = 0x%hhx\n", c);
220 
221         if (escape_level == 0) {
222             switch (c) {
223             case '\r':
224             case '\n':
225                 if (echo)
226                     cputchar('\n');
227                 goto done;
228 
229             case 0x7f: // backspace or delete
230             case 0x8:
231                 if (pos > 0) {
232                     pos--;
233                     cputs("\b \b"); // wipe out a character
234                 }
235                 break;
236 
237             case 0x1b: // escape
238                 escape_level++;
239                 break;
240 
241             default:
242                 buffer[pos++] = c;
243                 if (echo)
244                     cputchar(c);
245             }
246         } else if (escape_level == 1) {
247             // inside an escape, look for '['
248             if (c == '[') {
249                 escape_level++;
250             } else {
251                 // we didn't get it, abort
252                 escape_level = 0;
253             }
254         } else { // escape_level > 1
255             switch (c) {
256             case 67: // right arrow
257                 buffer[pos++] = ' ';
258                 if (echo)
259                     cputchar(' ');
260                 break;
261             case 68: // left arrow
262                 if (pos > 0) {
263                     pos--;
264                     if (echo) {
265                         cputs("\b \b"); // wipe out a character
266                     }
267                 }
268                 break;
269 #if CONSOLE_ENABLE_HISTORY
270             case 65: // up arrow -- previous history
271             case 66: // down arrow -- next history
272                 // wipe out the current line
273                 while (pos > 0) {
274                     pos--;
275                     if (echo) {
276                         cputs("\b \b"); // wipe out a character
277                     }
278                 }
279 
280                 if (c == 65)
281                     strlcpy(buffer, prev_history(&history_cursor), LINE_LEN);
282                 else
283                     strlcpy(buffer, next_history(&history_cursor), LINE_LEN);
284                 pos = strlen(buffer);
285                 if (echo)
286                     cputs(buffer);
287                 break;
288 #endif
289             default:
290                 break;
291             }
292             escape_level = 0;
293         }
294 
295         /* end of line. */
296         if (pos == (LINE_LEN - 1)) {
297             cputs("\nerror: line too long\n");
298             pos = 0;
299             goto done;
300         }
301     }
302 
303 done:
304     //  dprintf("returning pos %d\n", pos);
305 
306     // null terminate
307     buffer[pos] = 0;
308 
309 #if CONSOLE_ENABLE_HISTORY
310     // add to history
311     add_history(buffer);
312 #endif
313 
314     // return a pointer to our buffer
315     *outbuffer = buffer;
316 
317     return static_cast<int>(pos);
318 }
319 
tokenize_command(const char * inbuffer,const char ** continuebuffer,char * buffer,size_t buflen,cmd_args * args,int arg_count)320 static int tokenize_command(const char* inbuffer, const char** continuebuffer,
321                             char* buffer, size_t buflen, cmd_args* args, int arg_count) {
322     size_t inpos;
323     size_t outpos;
324     int arg;
325     enum {
326         INITIAL = 0,
327         NEXT_FIELD,
328         SPACE,
329         IN_SPACE,
330         TOKEN,
331         IN_TOKEN,
332         QUOTED_TOKEN,
333         IN_QUOTED_TOKEN,
334         VAR,
335         IN_VAR,
336         COMMAND_SEP,
337     } state;
338     char varname[128];
339     size_t varnamepos;
340 
341     inpos = 0;
342     outpos = 0;
343     arg = 0;
344     varnamepos = 0;
345     state = INITIAL;
346     *continuebuffer = NULL;
347 
348     for (;;) {
349         char c = inbuffer[inpos];
350 
351         //      dprintf(SPEW, "c 0x%hhx state %d arg %d inpos %zu pos %zu\n", c, state, arg, inpos, outpos);
352 
353         switch (state) {
354         case INITIAL:
355         case NEXT_FIELD:
356             if (c == '\0')
357                 goto done;
358             if (isspace(c))
359                 state = SPACE;
360             else if (c == ';')
361                 state = COMMAND_SEP;
362             else
363                 state = TOKEN;
364             break;
365         case SPACE:
366             state = IN_SPACE;
367             break;
368         case IN_SPACE:
369             if (c == '\0')
370                 goto done;
371             if (c == ';') {
372                 state = COMMAND_SEP;
373             } else if (!isspace(c)) {
374                 state = TOKEN;
375             } else {
376                 inpos++; // consume the space
377             }
378             break;
379         case TOKEN:
380             // start of a token
381             DEBUG_ASSERT(c != '\0');
382             if (c == '"') {
383                 // start of a quoted token
384                 state = QUOTED_TOKEN;
385             } else if (c == '$') {
386                 // start of a variable
387                 state = VAR;
388             } else {
389                 // regular, unquoted token
390                 state = IN_TOKEN;
391                 args[arg].str = &buffer[outpos];
392             }
393             break;
394         case IN_TOKEN:
395             if (c == '\0') {
396                 arg++;
397                 goto done;
398             }
399             if (isspace(c) || c == ';') {
400                 arg++;
401                 buffer[outpos] = 0;
402                 outpos++;
403                 /* are we out of tokens? */
404                 if (arg == arg_count)
405                     goto done;
406                 state = NEXT_FIELD;
407             } else {
408                 buffer[outpos] = c;
409                 outpos++;
410                 inpos++;
411             }
412             break;
413         case QUOTED_TOKEN:
414             // start of a quoted token
415             DEBUG_ASSERT(c == '"');
416 
417             state = IN_QUOTED_TOKEN;
418             args[arg].str = &buffer[outpos];
419             inpos++; // consume the quote
420             break;
421         case IN_QUOTED_TOKEN:
422             if (c == '\0') {
423                 arg++;
424                 goto done;
425             }
426             if (c == '"') {
427                 arg++;
428                 buffer[outpos] = 0;
429                 outpos++;
430                 /* are we out of tokens? */
431                 if (arg == arg_count)
432                     goto done;
433 
434                 state = NEXT_FIELD;
435             }
436             buffer[outpos] = c;
437             outpos++;
438             inpos++;
439             break;
440         case VAR:
441             DEBUG_ASSERT(c == '$');
442 
443             state = IN_VAR;
444             args[arg].str = &buffer[outpos];
445             inpos++; // consume the dollar sign
446 
447             // initialize the place to store the variable name
448             varnamepos = 0;
449             break;
450         case IN_VAR:
451             if (c == '\0' || isspace(c) || c == ';') {
452                 // hit the end of variable, look it up and stick it inline
453                 varname[varnamepos] = 0;
454 #if WITH_LIB_ENV
455                 int rc = env_get(varname, &buffer[outpos], buflen - outpos);
456 #else
457                 (void)varname[0]; // nuke a warning
458                 int rc = -1;
459 #endif
460                 if (rc < 0) {
461                     buffer[outpos++] = '0';
462                     buffer[outpos++] = 0;
463                 } else {
464                     outpos += strlen(&buffer[outpos]) + 1;
465                 }
466                 arg++;
467                 /* are we out of tokens? */
468                 if (arg == arg_count)
469                     goto done;
470 
471                 state = NEXT_FIELD;
472             } else {
473                 varname[varnamepos] = c;
474                 varnamepos++;
475                 inpos++;
476             }
477             break;
478         case COMMAND_SEP:
479             // we hit a ;, so terminate the command and pass the remainder of the command back in continuebuffer
480             DEBUG_ASSERT(c == ';');
481 
482             inpos++; // consume the ';'
483             *continuebuffer = &inbuffer[inpos];
484             goto done;
485         }
486     }
487 
488 done:
489     buffer[outpos] = 0;
490     return arg;
491 }
492 
convert_args(int argc,cmd_args * argv)493 static void convert_args(int argc, cmd_args* argv) {
494     int i;
495 
496     for (i = 0; i < argc; i++) {
497         unsigned long u = atoul(argv[i].str);
498         argv[i].u = u;
499         argv[i].p = (void*)u;
500         argv[i].i = atol(argv[i].str);
501 
502         if (!strcmp(argv[i].str, "true") || !strcmp(argv[i].str, "on")) {
503             argv[i].b = true;
504         } else if (!strcmp(argv[i].str, "false") || !strcmp(argv[i].str, "off")) {
505             argv[i].b = false;
506         } else {
507             argv[i].b = (argv[i].u == 0) ? false : true;
508         }
509     }
510 }
511 
command_loop(int (* get_line)(const char **,void *),void * get_line_cookie,bool showprompt,bool locked)512 static zx_status_t command_loop(int (*get_line)(const char**, void*),
513                                 void* get_line_cookie, bool showprompt,
514                                 bool locked) TA_NO_THREAD_SAFETY_ANALYSIS {
515     bool exit;
516 #if WITH_LIB_ENV
517     bool report_result;
518 #endif
519     cmd_args* args = NULL;
520     const char* buffer;
521     const char* continuebuffer;
522     char* outbuf = NULL;
523 
524     args = (cmd_args*)malloc(MAX_NUM_ARGS * sizeof(cmd_args));
525     if (unlikely(args == NULL)) {
526         return ZX_ERR_NO_MEMORY;
527     }
528 
529     const size_t outbuflen = 1024;
530     outbuf = static_cast<char*>(malloc(outbuflen));
531     if (unlikely(outbuf == NULL)) {
532         free(args);
533         return ZX_ERR_NO_MEMORY;
534     }
535 
536     exit = false;
537     continuebuffer = NULL;
538     while (!exit) {
539         // read a new line if it hadn't been split previously and passed back from tokenize_command
540         if (continuebuffer == NULL) {
541             if (showprompt)
542                 cputs("] ");
543 
544             int len = get_line(&buffer, get_line_cookie);
545             if (len < 0)
546                 break;
547             if (len == 0)
548                 continue;
549         } else {
550             buffer = continuebuffer;
551         }
552 
553         //      dprintf("line = '%s'\n", buffer);
554 
555         /* tokenize the line */
556         int argc = tokenize_command(buffer, &continuebuffer, outbuf, outbuflen,
557                                     args, MAX_NUM_ARGS);
558         if (argc < 0) {
559             if (showprompt)
560                 printf("syntax error\n");
561             continue;
562         } else if (argc == 0) {
563             continue;
564         }
565 
566         //      dprintf("after tokenize: argc %d\n", argc);
567         //      for (int i = 0; i < argc; i++)
568         //          dprintf("%d: '%s'\n", i, args[i].str);
569 
570         /* convert the args */
571         convert_args(argc, args);
572 
573         /* try to match the command */
574         const cmd* command = match_command(args[0].str, CMD_AVAIL_NORMAL);
575         if (!command) {
576             printf("command \"%s\" not found\n", args[0].str);
577             continue;
578         }
579 
580         if (!locked)
581             mutex_acquire(&command_lock);
582 
583         abort_script = false;
584         lastresult = command->cmd_callback(argc, args, 0);
585 
586 #if WITH_LIB_ENV
587         bool report_result;
588         env_get_bool("reportresult", &report_result, false);
589         if (report_result) {
590             if (lastresult < 0)
591                 printf("FAIL %d\n", lastresult);
592             else
593                 printf("PASS %d\n", lastresult);
594         }
595 #endif
596 
597 #if WITH_LIB_ENV
598         // stuff the result in an environment var
599         env_set_int("?", lastresult, true);
600 #endif
601 
602         // someone must have aborted the current script
603         if (abort_script)
604             exit = true;
605         abort_script = false;
606 
607         if (!locked)
608             mutex_release(&command_lock);
609     }
610 
611     free(outbuf);
612     free(args);
613     return ZX_OK;
614 }
615 
console_abort_script(void)616 void console_abort_script(void) {
617     abort_script = true;
618 }
619 
console_start(void)620 static void console_start(void) {
621     debug_buffer = static_cast<char*>(malloc(LINE_LEN));
622 
623     dprintf(INFO, "entering main console loop\n");
624 
625     while (command_loop(&read_debug_line, NULL, true, false) == ZX_OK)
626         ;
627 
628     dprintf(INFO, "exiting main console loop\n");
629 
630     free(debug_buffer);
631 }
632 
633 struct line_read_struct {
634     const char* string;
635     int pos;
636     char* buffer;
637     size_t buflen;
638 };
639 
fetch_next_line(const char ** buffer,void * cookie)640 static int fetch_next_line(const char** buffer, void* cookie) {
641     struct line_read_struct* lineread = (struct line_read_struct*)cookie;
642 
643     // we're done
644     if (lineread->string[lineread->pos] == 0)
645         return -1;
646 
647     size_t bufpos = 0;
648     while (lineread->string[lineread->pos] != 0) {
649         if (lineread->string[lineread->pos] == '\n') {
650             lineread->pos++;
651             break;
652         }
653         if (bufpos == (lineread->buflen - 1))
654             break;
655         lineread->buffer[bufpos] = lineread->string[lineread->pos];
656         lineread->pos++;
657         bufpos++;
658     }
659     lineread->buffer[bufpos] = 0;
660 
661     *buffer = lineread->buffer;
662 
663     return static_cast<int>(bufpos);
664 }
665 
console_run_script_etc(const char * string,bool locked)666 static int console_run_script_etc(const char* string, bool locked) {
667     struct line_read_struct lineread;
668 
669     lineread.string = string;
670     lineread.pos = 0;
671     lineread.buffer = static_cast<char*>(malloc(LINE_LEN));
672     lineread.buflen = LINE_LEN;
673 
674     command_loop(&fetch_next_line, (void*)&lineread, false, locked);
675 
676     free(lineread.buffer);
677 
678     return lastresult;
679 }
680 
console_run_script(const char * string)681 int console_run_script(const char* string) {
682     return console_run_script_etc(string, false);
683 }
684 
console_run_script_locked(const char * string)685 int console_run_script_locked(const char* string) {
686     return console_run_script_etc(string, true);
687 }
688 
console_get_command_handler(const char * commandstr)689 console_cmd* console_get_command_handler(const char* commandstr) {
690     const cmd* command = match_command(commandstr, CMD_AVAIL_NORMAL);
691 
692     if (command)
693         return command->cmd_callback;
694     else
695         return NULL;
696 }
697 
cmd_help(int argc,const cmd_args * argv,uint32_t flags)698 static int cmd_help(int argc, const cmd_args* argv, uint32_t flags) {
699     printf("command list:\n");
700 
701     /* filter out commands based on if we're called at normal or panic time */
702     uint8_t availability_mask = (flags & CMD_FLAG_PANIC) ? CMD_AVAIL_PANIC : CMD_AVAIL_NORMAL;
703 
704     for (const cmd* curr_cmd = __start_commands;
705          curr_cmd != __stop_commands;
706          ++curr_cmd) {
707         if ((availability_mask & curr_cmd->availability_mask) == 0) {
708             // Skip commands that aren't available in the current shell.
709             continue;
710         }
711         if (curr_cmd->help_str)
712             printf("\t%-16s: %s\n", curr_cmd->cmd_str, curr_cmd->help_str);
713     }
714 
715     return 0;
716 }
717 
cmd_echo(int argc,const cmd_args * argv,uint32_t flags)718 static int cmd_echo(int argc, const cmd_args* argv, uint32_t flags) {
719     if (argc > 1)
720         echo = argv[1].b;
721     return ZX_OK;
722 }
723 
panic_putc(char c)724 static void panic_putc(char c) {
725     platform_pputc(c);
726 }
727 
panic_puts(const char * str)728 static void panic_puts(const char* str) {
729     for (;;) {
730         char c = *str++;
731         if (c == 0) {
732             break;
733         }
734         platform_pputc(c);
735     }
736 }
737 
panic_getc(void)738 static int panic_getc(void) {
739     char c;
740     if (platform_pgetc(&c, false) < 0) {
741         return -1;
742     } else {
743         return c;
744     }
745 }
746 
read_line_panic(char * buffer,const size_t len)747 static void read_line_panic(char* buffer, const size_t len) {
748     size_t pos = 0;
749 
750     for (;;) {
751         int ci;
752         if ((ci = panic_getc()) < 0) {
753             continue;
754         }
755 
756         char c = static_cast<char>(ci);
757 
758         switch (c) {
759         case '\r':
760         case '\n':
761             panic_putc('\n');
762             goto done;
763         case 0x7f: // backspace or delete
764         case 0x8:
765             if (pos > 0) {
766                 pos--;
767                 panic_puts("\b \b"); // wipe out a character
768             }
769             break;
770         default:
771             buffer[pos++] = c;
772             panic_putc(c);
773         }
774         if (pos == (len - 1)) {
775             panic_puts("\nerror: line too long\n");
776             pos = 0;
777             goto done;
778         }
779     }
780 done:
781     buffer[pos] = 0;
782 }
783 
panic_shell_start(void)784 void panic_shell_start(void) {
785     dprintf(INFO, "entering panic shell loop\n");
786     char input_buffer[PANIC_LINE_LEN];
787     cmd_args args[MAX_NUM_ARGS];
788 
789     for (;;) {
790         panic_puts("! ");
791         read_line_panic(input_buffer, PANIC_LINE_LEN);
792 
793         int argc;
794         char* tok = strtok(input_buffer, WHITESPACE);
795         for (argc = 0; argc < MAX_NUM_ARGS; argc++) {
796             if (tok == NULL) {
797                 break;
798             }
799             args[argc].str = tok;
800             tok = strtok(NULL, WHITESPACE);
801         }
802 
803         if (argc == 0) {
804             continue;
805         }
806 
807         convert_args(argc, args);
808 
809         const cmd* command = match_command(args[0].str, CMD_AVAIL_PANIC);
810         if (!command) {
811             panic_puts("command not found\n");
812             continue;
813         }
814 
815         command->cmd_callback(argc, args, CMD_FLAG_PANIC);
816     }
817 }
818 
819 #if LK_DEBUGLEVEL > 1
cmd_test(int argc,const cmd_args * argv,uint32_t flags)820 static int cmd_test(int argc, const cmd_args* argv, uint32_t flags) {
821     int i;
822 
823     printf("argc %d, argv %p\n", argc, argv);
824     for (i = 0; i < argc; i++)
825         printf("\t%d: str '%s', i %ld, u %#lx, p %p, b %d\n", i,
826                argv[i].str, argv[i].i, argv[i].u, argv[i].p, argv[i].b);
827 
828     return 0;
829 }
830 #endif
831 
kernel_shell_init(uint level)832 static void kernel_shell_init(uint level) {
833     if (cmdline_get_bool("kernel.shell", false)) {
834         console_start();
835     }
836 }
837 
838 LK_INIT_HOOK(kernel_shell, kernel_shell_init, LK_INIT_LEVEL_USER);
839