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