1 /*
2 * Arm SCP/MCP Software
3 * Copyright (c) 2020-2021, Arm Limited and Contributors. All rights reserved.
4 *
5 * SPDX-License-Identifier: BSD-3-Clause
6 */
7
8 #include <cli.h>
9 #include <cli_config.h>
10 #include <cli_fifo.h>
11 #include <cli_platform.h>
12
13 #include <fwk_io.h>
14 #include <fwk_list.h>
15 #include <fwk_mm.h>
16
17 #include <stdarg.h>
18 #include <stdbool.h>
19 #include <stdint.h>
20 #include <stdlib.h>
21 #include <string.h>
22
23 /* String holding console prompt. */
24 static char cli_prompt[CLI_CONFIG_PROMPT_BUF_SIZE] = { 0 };
25 static char cli_prompt_size = 0;
26
27 /* Buffer to store a command as it is typed. */
28 static char cli_input_buffer[CLI_CONFIG_COMMAND_BUF_SIZE] = { 0 };
29
30 /* Array holding pointers to arguments after a command is parsed. */
31 static char *cli_args[CLI_CONFIG_MAX_NUM_ARGUMENTS] = { 0 };
32
33 /* Starting history buffer index. */
34 static uint32_t history_index = 0;
35 /* Buffer used to store the command history. */
36 static char *cli_history[CLI_CONFIG_HISTORY_LENGTH] = { 0 };
37 static char cli_history_buffer
38 [CLI_CONFIG_HISTORY_LENGTH * CLI_CONFIG_COMMAND_BUF_SIZE] = { 0 };
39
40 /* Default terminal size variables, these are updated at each enter press. */
41 static uint32_t cli_terminal_width = CLI_CONFIG_DEFAULT_TERM_W;
42 static uint32_t cli_terminal_height = CLI_CONFIG_DEFAULT_TERM_H;
43
44 /* Current state of the CLI. */
45 volatile cli_state_et cli_state = CLI_NOT_READY;
46
47 /* Extern link to buffer holding installed CLI commands. In cli_commands.h */
48 extern cli_command_st cli_commands[];
49
50 /* Print buffer structures. */
51 static fifo_st cli_print_fifo = { 0 };
52 static char cli_print_fifo_buffer[CLI_CONFIG_PRINT_BUFFER_SIZE] = { 0 };
53 static bool overflow = false;
54
strnlen(s,maxlen)55 size_t strnlen(s, maxlen) register const char *s;
56 size_t maxlen;
57 {
58 register const char *e;
59 size_t n;
60
61 for (e = s, n = 0; *e && n < maxlen; e++, n++)
62 ;
63 return n;
64 }
65
66 struct command_ctx {
67 cli_command_st cmd;
68 struct fwk_slist_node list_node;
69 };
70
71 /* A linked list used for extend the existing command at run time */
72 static struct fwk_slist cli_commands_list;
73
74 /* This array holds the common command available for all platforms */
75 extern cli_command_st cli_commands[];
76
cli_command_register(cli_command_st new_cmd)77 int cli_command_register(cli_command_st new_cmd)
78 {
79 struct fwk_slist *node = NULL;
80 struct command_ctx *c = NULL;
81
82 FWK_LIST_FOR_EACH(&cli_commands_list, node,
83 struct command_ctx, list_node, c) {
84 if (strcmp(new_cmd.command, c->cmd.command) == 0)
85 /* Command exist. Skip registration */
86 return FWK_SUCCESS;
87 }
88
89 c = fwk_mm_calloc(1, sizeof(struct command_ctx));
90 if (c == NULL)
91 return FWK_E_NOMEM;
92
93 c->cmd = new_cmd;
94 fwk_list_push_tail(&cli_commands_list, &c->list_node);
95
96 return FWK_SUCCESS;
97 }
98
cli_command_init(void)99 int cli_command_init(void)
100 {
101 uint32_t index;
102 int status;
103
104 fwk_list_init(&cli_commands_list);
105
106 /* Add the common commands to the commands linked list */
107 for (index = 0; cli_commands[index].command != 0; index++) {
108 status = cli_command_register(cli_commands[index]);
109 if (status != FWK_SUCCESS)
110 return status;
111 }
112 return FWK_SUCCESS;
113 }
114
115 /*
116 * TBA: Replace calls to strncmp with calls to cli_strncmp.
117 * For some reason calls to strncmp crash for seemingly no reason on the
118 * FPGA, so we'll use cli_strncmp until that gets fixed or we move over to
119 * silicon.
120 */
cli_strncmp(const char * s1,const char * s2,uint32_t limit)121 int32_t cli_strncmp(const char *s1, const char *s2, uint32_t limit)
122 {
123 for (uint32_t i = 0; i < limit; i++) {
124 if (s1[i] != s2[i])
125 /*
126 * If first string greater than second, return positive, if second
127 * string greater than first, return negative.
128 */
129 return s1[i] - s2[i];
130 else if (s1[i] == 0)
131 /*
132 * We know characters are equal, so if one of them is equal to zero
133 * both are, so return zero to indicate a match.
134 */
135 return 0;
136 }
137
138 /* Limit reached, return 0 as first string and second string match. */
139 /* up to the required character limit. */
140 return 0;
141 }
142
143 /*****************************************************************************/
144 /* Private function prototypes. */
145 /*****************************************************************************/
146
147 /*
148 * cli_main
149 * Description
150 * Main loop for CLI thread. Handles receiving and dispatching commands.
151 * Parameters
152 * void const *argument
153 * Argument passed at initialization, not used.
154 */
155 static void cli_main(void const *argument);
156
157 /*
158 * cli_format
159 * Description
160 * Sends formatting escape sequences to the terminal or writes them into a
161 * string depending on how it's used.
162 * Parameters
163 * cli_option_et options
164 * Formatting options to use.
165 * char *buffer
166 * If this value is not null, escape sequences are placed into this
167 * buffer instead of being sent to the terminal immediately.
168 * Return
169 * uint32_t: FWK_SUCCESS if it works, something else if it
170 * doesn't.
171 */
172 static uint32_t cli_format(cli_option_et options, char *buffer, uint32_t size);
173
174 /*
175 * cli_get_command
176 * Description
177 * Function called in cli_main that receives bytes from the UART,
178 * echos them back, and deals with backspace, escape sequences, etc.
179 * It returns the received command when the enter key is pressed.
180 * Parameters
181 * char *buffer
182 * Buffer in which the received command is placed.
183 * uint32_t buffer_size
184 * Total size, in bytes, of the command buffer.
185 * uint32_t *command_length
186 * The length of the command from the terminal is placed in this pointer.
187 * char history[][]
188 * Pointer to command history buffer.
189 * uint32_t history_index
190 * Index of the most recent item in the history buffer.
191 * uint32_t history_size
192 * Size of command history buffer.
193 * bool *exit
194 * Exit CLI was requested.
195 * Return
196 * uint32_t: FWK_SUCCESS if it works, something else if it
197 * doesn't.
198 */
199 static uint32_t cli_get_command(
200 char *buffer,
201 uint32_t buffer_size,
202 uint32_t *command_length,
203 char **history,
204 uint32_t history_index,
205 uint32_t history_size,
206 bool *exit);
207
208 /*
209 * cli_backspace
210 * Description
211 * Handles backspace characters from keyboard and makes sure lines wrap
212 * properly.
213 * Parameters
214 * uint32_t *cursor
215 * Pointer to cursor position variable.
216 * uint32_t width
217 * Width of terminal window.
218 * Return
219 * uint32_t: FWK_SUCCESS if it works, something else if it
220 * doesn't.
221 */
222 static uint32_t cli_backspace(uint32_t *cursor, uint32_t width);
223
224 /*
225 * cli_split
226 * Description
227 * Takes the command buffer, replaces whitespace characters with null
228 * characters, and builds an array of pointers to each individual argument.
229 * Arguments surrounded by parenthesis are taken verbatim, others are split
230 * up by whitespace.
231 * Parameters
232 * char **argbuf
233 * Buffer in which pointers to individual arguments are placed. Null
234 * terminated.
235 * uint32_t argbuf_size
236 * Number of entries in argbuf, can be configured with the
237 * MAX_NUM_ARGUMENTS macro above.
238 * char *command
239 * Command string to be parsed.
240 * uint32_t command_length
241 * Length of the command.
242 * char *whitespace
243 * Null-terminated string of characters to consider whitespace.
244 * Return
245 * uint32_t: FWK_SUCCESS if it works, something else if it
246 * doesn't.
247 */
248 static uint32_t cli_split(
249 char **argbuf,
250 uint32_t argbuf_size,
251 char *command,
252 uint32_t command_length,
253 const char *whitespace);
254
255 /*
256 * cli_command_dispatch
257 * Description
258 * Takes the argument list provided by cli_split and determines which
259 * command to execute.
260 * Parameters
261 * char **args
262 * Pointer to an array of strings containing the arguments received from
263 * the terminal.
264 * Return
265 * uint32_t: FWK_SUCCESS if it works, something else if it
266 * doesn't.
267 */
268 static uint32_t cli_command_dispatch(char **args);
269
270 /*
271 * cli_debug_output
272 * Description
273 * Handles the debug printout mode of the CLI. It runs until a Ctrl+C
274 * press is received.
275 * Return
276 * uint32_t: FWK_SUCCESS if it works, something else if it
277 * doesn't.
278 */
279 static uint32_t cli_debug_output(void);
280
281 /*
282 * cli_error_handler
283 * Description
284 * Prints information about cli_ret_et return values.
285 * Parameters
286 * cli_ret_et status
287 * Error code to print information about.
288 */
289 static void cli_error_handler(uint32_t status);
290
291 /*
292 * cli_val2str
293 * Description
294 * Converts a number variable to a string representation of the value.
295 * Parameters
296 * char **outstr
297 * Pointer to string in which to put the result.
298 * char *smax
299 * Pointer to the address after the last byte in outstr, prevents
300 * a buffer overflow.
301 * uint32_t value
302 * Value to convert to a string.
303 * uint32_t base
304 * Base of the result, usually base 2, 10, or 16.
305 * int32_t fill
306 * How many characters you want the result to take up.
307 */
308 static void cli_val2str(
309 char **outstr,
310 char *smax,
311 uint32_t value,
312 uint32_t base,
313 int32_t fill);
314
315 /*
316 * cli_snprintf_arg
317 * Description
318 * Same as cli_snprintf, but uses va_list arguments.
319 * Parameters
320 * char *s
321 * Buffer to build new string in.
322 * char *smax
323 * s + len(s), points to address after the last character in s. This
324 * keeps the function from exceeding the boundaries of the array.
325 * const char *fmt
326 * Formatted text to parse.
327 * va_list *args
328 * Pointer to initialized va_list structure.
329 */
330 static void cli_snprintf_arg(
331 char *s,
332 char *smax,
333 const char *fmt,
334 va_list *args);
335
336 volatile uint32_t automation_mode = 0;
337
338 /*****************************************************************************/
339 /* Public Function Definitions (see cli.h for descriptions) */
340 /*****************************************************************************/
341
cli_init(void)342 uint32_t cli_init(void)
343 {
344 int32_t status = FWK_SUCCESS;
345 char *prompt = CLI_PROMPT;
346
347 /* This function can only run when current state is CLI_NOT_READY. */
348 if (cli_state != CLI_NOT_READY)
349 return FWK_E_STATE;
350
351 /* Store CLI prompt text. */
352 if (strlen(prompt) > (CLI_CONFIG_PROMPT_BUF_SIZE - 1))
353 return FWK_E_NOMEM;
354
355 strncpy(cli_prompt, prompt, CLI_CONFIG_PROMPT_BUF_SIZE);
356 cli_prompt_size = strlen(prompt);
357
358 /* Initialize print buffer FIFO. */
359 status = fifo_init(
360 &cli_print_fifo, cli_print_fifo_buffer, CLI_CONFIG_PRINT_BUFFER_SIZE);
361 if (status != FWK_SUCCESS)
362 return status;
363
364 status = cli_command_init();
365 if (status != FWK_SUCCESS)
366 return status;
367
368 /* Setting state to CLI_READY. */
369 cli_state = CLI_READY;
370
371 return FWK_SUCCESS;
372 }
373
cli_start(void)374 uint32_t cli_start(void)
375 {
376 /* This function can only run when current state is CLI_READY. */
377 if (cli_state != CLI_READY)
378 return FWK_E_STATE;
379
380 /* Attempt to start thread. */
381 cli_main(NULL);
382
383 return FWK_SUCCESS;
384 }
385
cli_bprintf(cli_option_et options,const char * format,...)386 uint32_t cli_bprintf(cli_option_et options, const char *format, ...)
387 {
388 va_list arg;
389 uint32_t buffer_index = 0;
390 char scratch_buffer[CLI_CONFIG_SCRATCH_BUFFER_SIZE] = { 0 };
391
392 /* Check parameters. */
393 if (format == NULL)
394 return FWK_E_PARAM;
395
396 /* Check CLI state. */
397 if (cli_state == CLI_NOT_READY)
398 return FWK_E_STATE;
399
400 /* Check if we need any escape sequence formatting. */
401 if (options != 0) {
402 cli_format(options, scratch_buffer, CLI_CONFIG_SCRATCH_BUFFER_SIZE);
403 buffer_index = strnlen(scratch_buffer, CLI_CONFIG_SCRATCH_BUFFER_SIZE);
404 }
405
406 /* Generated formatted print string. */
407 va_start(arg, format);
408 cli_snprintf_arg(
409 (scratch_buffer + buffer_index),
410 (scratch_buffer + CLI_CONFIG_SCRATCH_BUFFER_SIZE),
411 format,
412 &arg);
413 va_end(arg);
414 buffer_index = strnlen(scratch_buffer, CLI_CONFIG_SCRATCH_BUFFER_SIZE);
415
416 /* Make sure to return formatting to normal after escape sequences are used.
417 */
418 if (options != NONE) {
419 strncat(
420 scratch_buffer,
421 "\x1B[0m",
422 (CLI_CONFIG_SCRATCH_BUFFER_SIZE - buffer_index));
423 buffer_index = strnlen(scratch_buffer, CLI_CONFIG_SCRATCH_BUFFER_SIZE);
424 }
425
426 /* Check to make sure we didn't fill up our scratch buffer. */
427 if (buffer_index >= CLI_CONFIG_SCRATCH_BUFFER_SIZE)
428 return FWK_E_NOMEM;
429
430 /* Send formatted print data to print FIFO. */
431 return cli_bprint(scratch_buffer);
432 }
433
cli_bprint(const char * string)434 uint32_t cli_bprint(const char *string)
435 {
436 uint32_t i = 0;
437 int32_t status = FWK_SUCCESS;
438
439 /* Check parameters. */
440 if (string == NULL)
441 return FWK_E_PARAM;
442
443 /* If not ready, return an error. */
444 if (cli_state == CLI_NOT_READY)
445 return FWK_E_STATE;
446
447 /* If ready but not running, print directly to UART. */
448 if (cli_state == CLI_READY)
449 return cli_print(string);
450
451 /* Put data into FIFO. */
452 for (i = 0; ; i++) {
453 if ((string[i] == 0) &&
454 (i >= CLI_CONFIG_SCRATCH_BUFFER_SIZE) &&
455 (status != FWK_SUCCESS))
456 break;
457 status = fifo_put(&cli_print_fifo, (char *)&string[i]);
458 }
459
460 /* Print an error message if the print buffer is full. */
461 if ((status != FWK_SUCCESS) && (overflow == false)) {
462 overflow = true;
463 cli_print("\x1B[31mCONSOLE ERROR:\x1B[0m Print buffer overflow.\n");
464 }
465
466 return status;
467 }
468 static char sCLIbuffer[CLI_CONFIG_SCRATCH_BUFFER_SIZE] = { 0 };
469
470 static const char str_format_buf_too_small[] =
471 "\x1B[31mCONSOLE ERROR:\x1B[0m CLI format print buffer too small.\n";
cli_printf(cli_option_et options,const char * format,...)472 uint32_t cli_printf(cli_option_et options, const char *format, ...)
473 {
474 va_list arg;
475 int32_t status = FWK_SUCCESS;
476
477 /* Checking pointers. */
478 if (format == 0)
479 return FWK_E_PARAM;
480
481 /* Applying style options. */
482 if (options != NONE) {
483 status = cli_format(options, 0, 0);
484 if (status != FWK_SUCCESS)
485 return status;
486 }
487
488 /* Printing formatted text. */
489 va_start(arg, format);
490 cli_snprintf_arg(
491 sCLIbuffer,
492 sCLIbuffer + CLI_CONFIG_SCRATCH_BUFFER_SIZE,
493 (char *)format,
494 &arg);
495 va_end(arg);
496
497 /* Print format string. */
498 status = cli_print((const char *)sCLIbuffer);
499 if (status != FWK_SUCCESS)
500 return status;
501
502 /* If style options were used, reset everything to default. */
503 if (options != NONE) {
504 cli_format(NONE, 0, 0);
505 if (status != FWK_SUCCESS)
506 return status;
507 }
508
509 /* Making sure we didn't try to print too much. */
510 if (strnlen((char *)sCLIbuffer, CLI_CONFIG_SCRATCH_BUFFER_SIZE) >=
511 CLI_CONFIG_SCRATCH_BUFFER_SIZE - 1) {
512 status = cli_print(str_format_buf_too_small);
513 if (status != FWK_SUCCESS)
514 return status;
515 return FWK_E_NOMEM;
516 }
517
518 return status;
519 }
520
cli_print(const char * string)521 uint32_t cli_print(const char *string)
522 {
523 uint32_t index = 0;
524 int32_t status = FWK_SUCCESS;
525
526 for (index = 0; string[index] != 0; index++) {
527 status = fwk_io_putch(fwk_io_stdout, string[index]);
528 if (status != FWK_SUCCESS)
529 return status;
530 }
531 return status;
532 }
533
cli_snprintf(char * s,char * smax,const char * fmt,...)534 void cli_snprintf(char *s, char *smax, const char *fmt, ...)
535 {
536 va_list args;
537
538 va_start(args, fmt);
539 cli_snprintf_arg(s, smax, fmt, &args);
540 va_end(args);
541
542 return;
543 }
544
cli_getline(char * buffer,uint32_t buffer_size,char ** argbuf,uint32_t argbuf_size,uint32_t cursor_position)545 uint32_t cli_getline(
546 char *buffer,
547 uint32_t buffer_size,
548 char **argbuf,
549 uint32_t argbuf_size,
550 uint32_t cursor_position)
551 {
552 char c = 0;
553 uint32_t index = 0;
554 int32_t status = FWK_SUCCESS;
555
556 /* Validate parameters. */
557 if (buffer == NULL ||
558 buffer_size == 0 ||
559 cursor_position >= cli_terminal_width ||
560 argbuf == NULL ||
561 argbuf_size == 0)
562 return FWK_E_PARAM;
563
564 /* Zero out buffer and argbuf. */
565 memset(buffer, 0, buffer_size);
566 memset(argbuf, 0, argbuf_size * sizeof(char **));
567
568 /* Print prompt arrow. */
569 status = cli_print(CLI_PROMPT);
570 if (status != FWK_SUCCESS)
571 return status;
572
573 /* Increment cursor position since we just printed an arrow and space. */
574 cursor_position = (cursor_position + 2) % cli_terminal_width;
575
576 /*
577 * Loop will terminate when the user presses enter or when an error is
578 * generated. This loop will have negligible impact on system performance.
579 */
580 while (1) {
581 /* Grab a character from the UART. */
582 do {
583 status = fwk_io_getch(fwk_io_stdin, &c);
584 } while (status == FWK_PENDING);
585
586 if (status != FWK_SUCCESS)
587 return status;
588
589 /*
590 * Ignore non-printing characters except for a few we care about.
591 * 0x00 - 0x1F are non-printing characters.
592 * 0x7F - 0xFF are non-printing characters.
593 * Carriage return: \r, 0x0D
594 * Newline: \n, 0x0C
595 * Backspace: \b, 0x08
596 * Delete: 0x7F
597 */
598 if ((c <= 0x1F || c >= 0x7F) && c != '\r' && c != '\n' && c != '\b' &&
599 c != 0x7F)
600 continue;
601
602
603 /* If backspace (0x08) or delete (0x7F) character received. */
604 if (c == '\b' || c == 0x7F) {
605 /* Only accept backspace presses if we're not at the beginning of
606 * the string. */
607 if (index != 0) {
608 status = cli_backspace(&cursor_position, cli_terminal_width);
609 if (status != FWK_SUCCESS)
610 return status;
611
612 /* Decrement index and set old last character to null. */
613 index = index - 1;
614 buffer[index] = 0;
615 }
616 continue;
617 }
618
619 /* If newline received. */
620 if (c == '\n' || c == '\r') {
621 /* Making sure the rest of the buffer is zero. */
622 memset(&(buffer[index]), 0, buffer_size - index);
623 status = cli_print("\n");
624 if (status != FWK_SUCCESS)
625 return status;
626 break;
627 }
628
629 /* Echo received character to console. */
630 do {
631 status = fwk_io_putch(fwk_io_stdout, c);
632 } while (status == FWK_PENDING);
633
634 if (status != FWK_SUCCESS)
635 return status;
636
637 cursor_position = (cursor_position + 1) % cli_terminal_width;
638
639 /* Incrementing indices. */
640 buffer[index] = c;
641 index = index + 1;
642 if (index >= buffer_size) {
643 /*
644 * Add null termination in case the user doesn't check return codes
645 * and tries to use the buffer.
646 */
647 buffer[index - 1] = 0;
648 return FWK_E_NOMEM;
649 }
650
651 /* Add new null terminator. */
652 buffer[index] = 0;
653 }
654
655 status = cli_split(argbuf, argbuf_size, buffer, index, " ");
656 if (status != FWK_SUCCESS)
657 return status;
658
659 return FWK_SUCCESS;
660 }
661
662 /*****************************************************************************/
663 /* Private Function Definitions */
664 /*****************************************************************************/
665
cli_main(void const * argument)666 static void cli_main(void const *argument)
667 {
668 int32_t status;
669 uint32_t last_history_index = CLI_CONFIG_HISTORY_LENGTH - 1;
670 uint32_t command_length = 0;
671 bool cli_exit = false;
672
673 /* Thread was started successfully, set state to CLI_RUNNING. */
674 cli_state = CLI_RUNNING;
675
676 /* Initialize command history buffer pointers. */
677 for (uint32_t i = 0; i < CLI_CONFIG_HISTORY_LENGTH; i++)
678 cli_history[i] = &cli_history_buffer[i * CLI_CONFIG_COMMAND_BUF_SIZE];
679
680 /* Loop forever. */
681 while (1) {
682 /* Printing prompt text. */
683 cli_printf(NONE, cli_prompt);
684
685 /* Zero out input buffer. */
686 memset(cli_input_buffer, 0, CLI_CONFIG_COMMAND_BUF_SIZE);
687
688 /* Get command from terminal UART. */
689 status = cli_get_command(
690 cli_input_buffer,
691 CLI_CONFIG_COMMAND_BUF_SIZE - 1,
692 &command_length,
693 cli_history,
694 history_index,
695 CLI_CONFIG_HISTORY_LENGTH,
696 &cli_exit);
697 if (status != FWK_SUCCESS) {
698 cli_error_handler(status);
699 continue;
700 }
701
702 /* Ctrl+d was pressed to exit cli */
703 if (cli_exit) {
704 cli_state = CLI_READY;
705 return;
706 }
707
708 /* Make sure command string is not empty. */
709 if (cli_input_buffer[0] == 0)
710 continue;
711
712
713 /* Update history buffer if command is different than the last one. */
714 if (cli_strncmp(
715 cli_input_buffer,
716 cli_history[last_history_index],
717 CLI_CONFIG_COMMAND_BUF_SIZE) != 0) {
718 strncpy(
719 cli_history[history_index],
720 cli_input_buffer,
721 CLI_CONFIG_COMMAND_BUF_SIZE);
722 history_index = (history_index + 1) % CLI_CONFIG_HISTORY_LENGTH;
723 last_history_index =
724 (last_history_index + 1) % CLI_CONFIG_HISTORY_LENGTH;
725 }
726
727 /* Splitting up command into individual argument strings. */
728 status = cli_split(
729 cli_args,
730 CLI_CONFIG_MAX_NUM_ARGUMENTS,
731 cli_input_buffer,
732 command_length,
733 " ");
734 if (status != FWK_SUCCESS) {
735 cli_error_handler(status);
736 continue;
737 }
738
739 /* If the user didn't type any valid arguments, don't process it. */
740 if (cli_args[0] == 0)
741 continue;
742
743 /* Dispatching command for processing. */
744 status = cli_command_dispatch(cli_args);
745 if (status != FWK_SUCCESS)
746 cli_error_handler(status);
747 }
748 }
749
cli_format(cli_option_et options,char * buffer,uint32_t size)750 uint32_t cli_format(cli_option_et options, char *buffer, uint32_t size)
751 {
752 int32_t status = FWK_SUCCESS;
753 static char tmp_buf[10] = { 0 };
754
755 if (buffer != NULL)
756 /* Add a null terminator before we do anything. */
757 buffer[0] = '\0';
758
759 if (automation_mode)
760 return status;
761
762 /* If no options given, send SGR default sequence to remove formatting. */
763 if (options == NONE) {
764 if (buffer != NULL) {
765 if (size < 5)
766 return FWK_E_NOMEM;
767 memcpy(buffer, "\x1B[0m", 5);
768 buffer = &(buffer[4]);
769 size = size - 4;
770 } else
771 return cli_print("\x1B[0m");
772 }
773 /* Clear terminal window. */
774 if (options & CLEAR_DISPLAY) {
775 if (buffer != NULL) {
776 if (size < 5)
777 return FWK_E_NOMEM;
778 memcpy(buffer, "\x1B[2J", 5);
779 buffer = &(buffer[4]);
780 size = size - 4;
781 } else
782 status = cli_print("\x1B[2J");
783
784 if (status != FWK_SUCCESS)
785 return status;
786 }
787 /* Reset cursor. */
788 if (options & RESET_CURSOR) {
789 if (buffer != NULL) {
790 if (size < 7)
791 return FWK_E_NOMEM;
792
793 memcpy(buffer, "\x1B[0;0f", 7);
794 buffer = &(buffer[6]);
795 size = size - 6;
796 } else
797 status = cli_print("\x1B[0;0f");
798
799 if (status != FWK_SUCCESS)
800 return status;
801 }
802 /* SGR settings. */
803 if (options & CLI_ALL_MASK) {
804 if (buffer != NULL) {
805 if (size < 3)
806 return FWK_E_NOMEM;
807 memcpy(buffer, "\x1B[", 3);
808 buffer = &(buffer[2]);
809 size = size - 2;
810 } else {
811 status = cli_print("\x1B[");
812 if (status != FWK_SUCCESS)
813 return status;
814 }
815 /* Bold/bright. */
816 if (options & BOLD) {
817 if (buffer != NULL) {
818 if (size < 3)
819 return FWK_E_NOMEM;
820
821 memcpy(buffer, ";1", 3);
822 buffer = &(buffer[2]);
823 size = size - 2;
824 } else {
825 status = cli_print(";1");
826 if (status != FWK_SUCCESS)
827 return status;
828 }
829 }
830 /* Underlining. */
831 if (options & UNDERLINE) {
832 if (buffer != NULL) {
833 if (size < 3)
834 return FWK_E_NOMEM;
835 memcpy(buffer, ";4", 3);
836 buffer = &(buffer[2]);
837 size = size - 2;
838 } else {
839 status = cli_print(";4");
840 if (status != FWK_SUCCESS)
841 return status;
842 }
843 }
844 /* Background color. */
845 if (options & CLI_BG_COLOR_MASK) {
846 if (buffer != NULL) {
847 if (size < 4)
848 return FWK_E_NOMEM;
849
850 cli_snprintf(
851 tmp_buf,
852 tmp_buf + 10,
853 (const char *)";%d",
854 (uint16_t)(
855 (options & CLI_BG_COLOR_MASK) >> CLI_BG_COLOR_SHIFT));
856 memcpy(buffer, tmp_buf, 4);
857 buffer = &(buffer[3]);
858 size = size - 3;
859 } else {
860 status = cli_printf(
861 0,
862 ";%d",
863 (uint16_t)(
864 (options & CLI_BG_COLOR_MASK) >> CLI_BG_COLOR_SHIFT));
865 if (status != FWK_SUCCESS)
866 return status;
867 }
868 }
869 /* Foreground color. */
870 if (options & CLI_TEXT_COLOR_MASK) {
871 if (buffer != NULL) {
872 if (size < 4)
873 return FWK_E_NOMEM;
874 cli_snprintf(
875 tmp_buf,
876 tmp_buf + 10,
877 (const char *)";%d",
878 (char)((options & CLI_TEXT_COLOR_MASK) >>
879 CLI_TEXT_COLOR_SHIFT));
880 memcpy(buffer, tmp_buf, 4);
881 buffer = &(buffer[3]);
882 size = size - 3;
883 } else {
884 status = cli_printf(
885 0,
886 ";%d",
887 (char)((options & CLI_TEXT_COLOR_MASK) >>
888 CLI_TEXT_COLOR_SHIFT));
889 if (status != FWK_SUCCESS)
890 return status;
891 }
892 }
893 if (buffer != NULL) {
894 if (size < 2)
895 return FWK_E_NOMEM;
896 memcpy(buffer, "m", 2);
897 } else {
898 status = cli_print("m");
899 if (status != FWK_SUCCESS)
900 return status;
901 }
902 }
903
904 return FWK_SUCCESS;
905 }
906
cli_get_command(char * buffer,uint32_t buffer_size,uint32_t * command_length,char ** history,uint32_t history_index,uint32_t history_size,bool * exit)907 static uint32_t cli_get_command(
908 char *buffer,
909 uint32_t buffer_size,
910 uint32_t *command_length,
911 char **history,
912 uint32_t history_index,
913 uint32_t history_size,
914 bool *exit)
915 {
916 int32_t status = FWK_SUCCESS;
917 uint32_t index = 0;
918 uint32_t cursor_index = cli_prompt_size;
919 char c = 0;
920 static char escape[8] = { 0 };
921 uint32_t escape_index = 0;
922 uint32_t history_oldest = history_index;
923 bool flag_escape_sequence = false;
924
925 (void)escape;
926
927 /* Checking parameters. */
928 if (buffer == 0 || buffer_size == 0 || history == 0 ||
929 command_length == 0)
930 return FWK_E_PARAM;
931
932 if (!automation_mode) {
933 /* Getting terminal window size. */
934 /* Saving cursor position. */
935 cli_print("\x1B[s");
936 /* Moving cursor to bottom right position. */
937 cli_print("\x1B[999;999f");
938 /* Requesting new cursor position. */
939 cli_print("\x1B[6n");
940 /* Restoring old cursor position. */
941 cli_print("\x1B[u");
942 }
943
944 while (1) {
945 /* Get character from UART. */
946 do {
947 status = fwk_io_getch(fwk_io_stdin, &c);
948 } while (status == FWK_PENDING);
949
950 if (status != FWK_SUCCESS)
951 return status;
952
953 /* If we received a Ctrl+C press, go to debug mode. */
954 if (c == '\x03') {
955 *command_length = 0;
956 return cli_debug_output();
957 }
958
959 /* If we received a Ctrl+d press, exit cli. */
960 if (c == '\x04') {
961 *exit = true;
962 return FWK_SUCCESS;
963 }
964
965 /* Ignoring non-printing characters except for a few we care about. */
966 if (c < 0x20 && c != '\r' && c != '\n' && c != '\b') {
967 if (c == 0x1B)
968 flag_escape_sequence = true;
969 continue;
970 }
971
972 /* Dealing with escape sequences. */
973 if (flag_escape_sequence == true) {
974 escape[escape_index] = c;
975 escape[escape_index + 1] = 0;
976 escape_index = escape_index + 1;
977
978 /* Escape sequences end with a letter. */
979 if ((c > 0x40 && c < 0x5B) || (c > 0x60 && c < 0x7B)) {
980 flag_escape_sequence = false;
981 escape_index = 0;
982
983 /* Up arrow press. */
984 if (c == 'A') {
985 if (((history_oldest + 1) % history_size) !=
986 history_index) {
987 /* Rewind history index by 1. */
988 if (history_index == 0)
989 history_index = history_size - 1;
990 else
991 history_index = history_index - 1;
992
993 /* If command entry is empty then stop and restore index
994 * value. */
995 if (history[history_index][0] == 0) {
996 history_index = (history_index + 1) % history_size;
997 } else {
998 /* Erasing currently entered command. */
999 for (; index > 0; index--) {
1000 status = cli_backspace(
1001 &cursor_index, cli_terminal_width);
1002 if (status != FWK_SUCCESS)
1003 return status;
1004 }
1005
1006 /* Copying history command from history buffer. */
1007 strncpy(
1008 buffer, history[history_index], buffer_size);
1009
1010 /* Printing history command to screen. */
1011 while (buffer[index] != 0) {
1012 status =
1013 fwk_io_putch(fwk_io_stdout, buffer[index]);
1014 if (status != FWK_SUCCESS)
1015 return status;
1016 index = index + 1;
1017 cursor_index =
1018 (cursor_index + 1) % cli_terminal_width;
1019 }
1020 }
1021 }
1022 }
1023
1024 /* Down arrow press. */
1025 if (c == 'B') {
1026 if (history_index != history_oldest) {
1027 /* Getting index of history item to load. */
1028 history_index = (history_index + 1) % history_size;
1029
1030 /* Erasing everything in the currently entered command.
1031 */
1032 for (; index > 0; index--) {
1033 status = cli_backspace(
1034 &cursor_index, cli_terminal_width);
1035 if (status != FWK_SUCCESS)
1036 return status;
1037 }
1038
1039 /* If we're back to the current command start fresh and
1040 * zero buffer. */
1041 if (history_index == history_oldest)
1042 memset(buffer, 0, buffer_size);
1043 else {
1044 /* Copying history command from history buffer. */
1045 strncpy(
1046 buffer, history[history_index], buffer_size);
1047
1048 /* Printing history command to screen. */
1049 while (buffer[index] != 0) {
1050 status =
1051 fwk_io_putch(fwk_io_stdout, buffer[index]);
1052 if (status != FWK_SUCCESS)
1053 return status;
1054
1055 index = index + 1;
1056 cursor_index =
1057 (cursor_index + 1) % cli_terminal_width;
1058 }
1059 }
1060 }
1061 }
1062
1063 /* Handling cursor position response sequence. */
1064 if (c == 'R') {
1065 uint32_t i = 0;
1066 for (i = 0; escape[i] != '['; i++)
1067 ;
1068 i++;
1069 cli_terminal_height = strtoul(&escape[i], NULL, 0);
1070 for (; escape[i] != ';'; i++)
1071 ;
1072 i++;
1073 cli_terminal_width = strtoul(&escape[i], NULL, 0);
1074 }
1075 }
1076 continue;
1077 }
1078
1079 /* If backspace (0x08) or delete (0x7F) character received. */
1080 if (c == 0x08 || c == 0x7F) {
1081 /* Only accept backspace presses if we're not at the beginning of
1082 * the string. */
1083 if (index != 0) {
1084 status = cli_backspace(&cursor_index, cli_terminal_width);
1085 if (status != FWK_SUCCESS)
1086 return status;
1087 index = index - 1;
1088 buffer[index] = 0;
1089 }
1090 continue;
1091 }
1092
1093 /* If newline received. */
1094 if (c == '\n' || c == '\r') {
1095 /* Making sure the rest of the buffer is zero. */
1096 memset(&(buffer[index]), 0, buffer_size - index);
1097 *command_length = index;
1098 return cli_print("\n");
1099 }
1100
1101 /* Printing received character to console. */
1102 status = fwk_io_putch(fwk_io_stdout, c);
1103 if (status != FWK_SUCCESS)
1104 return status;
1105
1106 /* Incrementing indices. */
1107 buffer[index] = c;
1108 index = index + 1;
1109 if (index >= buffer_size)
1110 return FWK_E_NOMEM;
1111 buffer[index] = 0;
1112 cursor_index = (cursor_index + 1) % cli_terminal_width;
1113 }
1114 }
1115
cli_backspace(uint32_t * cursor,uint32_t width)1116 static uint32_t cli_backspace(uint32_t *cursor, uint32_t width)
1117 {
1118 uint32_t status = FWK_SUCCESS;
1119
1120 /* If cursor is at the first position of a line. */
1121 if (*cursor == 0) {
1122 status = cli_printf(0, "\x1B[A\x1B[%dC ", width - 1);
1123 if (status != FWK_SUCCESS)
1124 return status;
1125 *cursor = width - 1;
1126 } else {
1127 /* For compatibility, print back, space, back. */
1128 status = cli_print("\x1B[D \x1B[D");
1129 if (status != FWK_SUCCESS)
1130 return status;
1131 *cursor = *cursor - 1;
1132 }
1133
1134 return FWK_SUCCESS;
1135 }
1136
cli_split(char ** argbuf,uint32_t argbuf_size,char * command,uint32_t command_length,const char * whitespace)1137 static uint32_t cli_split(
1138 char **argbuf,
1139 uint32_t argbuf_size,
1140 char *command,
1141 uint32_t command_length,
1142 const char *whitespace)
1143 {
1144 uint32_t index = 0;
1145 uint32_t argbuf_ctr = 0;
1146 bool flag_paren = false;
1147 bool flag_last_was_whitespace = true;
1148
1149 /* Checking pointers. */
1150 if (argbuf == 0 || command == 0 || argbuf_size == 0 || whitespace == 0)
1151 return FWK_E_PARAM;
1152
1153 argbuf[0] = 0;
1154
1155 for (index = 0; index < command_length; index++) {
1156 /* If whitespace is encountered outside of parenthesis, change it to
1157 * null and set flag. */
1158 if (strchr(whitespace, command[index]) != 0 && flag_paren == false) {
1159 command[index] = 0;
1160 flag_last_was_whitespace = true;
1161 continue;
1162 }
1163
1164 /* Handle parenthesis. */
1165 if (command[index] == '\"') {
1166 /* If we've reached the end of an arg in parenthesis, reset flag. */
1167 if (flag_paren == true) {
1168 flag_paren = false;
1169 command[index] = 0;
1170 flag_last_was_whitespace = true;
1171 continue;
1172 }
1173
1174 /* If we receive an opening parenthesis preceded by a whitespace
1175 * character, mark null and enter parenthesis processing. */
1176 if (flag_paren == false && flag_last_was_whitespace == true) {
1177 flag_paren = true;
1178 command[index] = 0;
1179 continue;
1180 }
1181 }
1182
1183 /* First regular character after whitespace encountered. */
1184 if ((strchr(whitespace, command[index]) == 0 || flag_paren == true) &&
1185 flag_last_was_whitespace == true) {
1186 flag_last_was_whitespace = false;
1187 if (argbuf_ctr + 1 >= argbuf_size)
1188 return FWK_E_NOMEM;
1189 argbuf[argbuf_ctr] = &command[index];
1190 argbuf[argbuf_ctr + 1] = 0;
1191 argbuf_ctr = argbuf_ctr + 1;
1192 }
1193 }
1194
1195 /* If the user forgot to close their parenthesis, return an error. */
1196 if (flag_paren == true)
1197 return FWK_E_PARAM;
1198
1199 return FWK_SUCCESS;
1200 }
1201
cli_command_dispatch(char ** args)1202 static uint32_t cli_command_dispatch(char **args)
1203 {
1204 uint32_t index;
1205 uint32_t status;
1206 uint32_t num_args = 0;
1207 struct fwk_slist *node = NULL;
1208 struct command_ctx *cc = NULL;
1209 bool command_found;
1210
1211 /* Checking pointer. */
1212 if (args == 0)
1213 return FWK_E_PARAM;
1214
1215 /* Special case command: help. */
1216 if (cli_strncmp(args[0], "help", 5) == 0) {
1217 FWK_LIST_FOR_EACH(&cli_commands_list, node,
1218 struct command_ctx, list_node, cc) {
1219 cli_printf(NONE, "%s\n", cc->cmd.command);
1220 cli_print(cc->cmd.help);
1221 cli_print("\n");
1222 }
1223
1224 cli_printf(NONE, "help\n");
1225 cli_print(" Displays this information.\n");
1226 cli_printf(NONE, "Ctrl+C\n");
1227 cli_print(" Displays debug output from running threads.\n");
1228 cli_printf(NONE, "Ctrl+d\n");
1229 cli_print(" Exit the CLI.\n");
1230 return FWK_SUCCESS;
1231 }
1232
1233 if (cli_strncmp(args[0], "AUTO", 4) == 0) {
1234 cli_printf(NONE, "AUTO Mode ON\n");
1235 automation_mode = 1;
1236 return FWK_SUCCESS;
1237 }
1238
1239 if (cli_strncmp(args[0], "OTUA", 4) == 0) {
1240 cli_printf(NONE, "AUTO Mode OFF\n");
1241 automation_mode = 0;
1242 return FWK_SUCCESS;
1243 }
1244
1245 /* Searching for command handler. */
1246 /* Using strcmp here because each entry in args is guaranteed to be null
1247 * terminated by cli_split. */
1248 command_found = false;
1249 FWK_LIST_FOR_EACH(&cli_commands_list, node,
1250 struct command_ctx, list_node, cc) {
1251 if (strcmp(args[0], cc->cmd.command) == 0) {
1252 command_found = true;
1253 break;
1254 }
1255 }
1256 if (!command_found)
1257 return FWK_E_SUPPORT;
1258
1259 /* Handler found, if -h or --help is given just print it's help string and
1260 * return. */
1261 if (cc->cmd.ignore_help_flag == false) {
1262 for (index = 1;
1263 (args[index] != 0) && (cli_strncmp(args[index], "-h", 3) != 0) &&
1264 (cli_strncmp(args[index], "--help", 7) != 0);
1265 index++)
1266 ;
1267
1268 if ((args[index] != 0 || cc->cmd.handler == 0) &&
1269 cc->cmd.help != 0) {
1270 status = cli_print(cc->cmd.help);
1271 if (status != FWK_SUCCESS)
1272 return status;
1273
1274 /* Print a newline since help strings shouldn't have newlines at the
1275 * end. */
1276 cli_print("\n");
1277 return status;
1278 }
1279 }
1280
1281 /* Counting arguments. */
1282 for (num_args = 0; args[num_args] != 0; num_args++)
1283 ;
1284
1285 /* Calling command handler. */
1286 /* args is incremented so the command just gets the arguments and not the
1287 * name of itself. */
1288 if (cc->cmd.handler != 0)
1289 return cc->cmd.handler(num_args, args);
1290 else
1291 return FWK_E_PARAM;
1292 }
1293
cli_debug_output(void)1294 static uint32_t cli_debug_output(void)
1295 {
1296 uint32_t fifo_status;
1297 char c = 0;
1298
1299 cli_printf(
1300 0,
1301 "\nNow showing debug console output, to return to the command line, "
1302 "press Ctrl+C.\n");
1303 while (1) {
1304 /* Looking for Ctrl+C press. */
1305 if (fwk_io_getch(fwk_io_stdin, &c) == FWK_SUCCESS) {
1306 if (c == '\x03') {
1307 cli_printf(
1308 0,
1309 "\nNow showing command line, to return to debug output, "
1310 "press Ctrl+C.\n");
1311 return FWK_SUCCESS;
1312 }
1313 }
1314
1315 /* Read from print FIFO. */
1316 fifo_status = fifo_get(&cli_print_fifo, &c);
1317 if (fifo_status == FWK_SUCCESS) {
1318 overflow = false;
1319 fwk_io_putch(fwk_io_stdout, c);
1320 } else
1321 /* If no characters are available, let other stuff run. */
1322 cli_platform_delay_ms(0);
1323 }
1324 }
1325
cli_error_handler(uint32_t status)1326 static void cli_error_handler(uint32_t status)
1327 {
1328 if (status == FWK_SUCCESS)
1329 return;
1330
1331 cli_printf(NONE, "CONSOLE ERROR: %s\n", fwk_status_str(status));
1332 }
1333
cli_val2str(char ** outstr,char * smax,uint32_t value,uint32_t base,int32_t fill)1334 static void cli_val2str(
1335 char **outstr,
1336 char *smax,
1337 uint32_t value,
1338 uint32_t base,
1339 int32_t fill)
1340 {
1341 /* Just need enough space to store 64 bit decimal integer */
1342 unsigned char str[20] = { 0 };
1343 int i = 0;
1344
1345 do {
1346 str[i++] = "0123456789ABCDEF"[value % base];
1347 } while (value /= base);
1348
1349 while (--fill >= i) {
1350 **outstr = '0';
1351 *outstr = *outstr + 1;
1352 if (*outstr >= smax)
1353 return;
1354 }
1355
1356 while (--i >= 0) {
1357 **outstr = str[i];
1358 *outstr = *outstr + 1;
1359 if (*outstr >= smax)
1360 return;
1361 }
1362 }
1363
cli_snprintf_arg(char * s,char * smax,const char * fmt,va_list * args)1364 static void cli_snprintf_arg(
1365 char *s,
1366 char *smax,
1367 const char *fmt,
1368 va_list *args)
1369 {
1370 int bit64;
1371 int fill;
1372 int64_t num = 0;
1373 uint64_t unum = 0;
1374 char *str = NULL;
1375 uint32_t most_significant = 0;
1376 char c = 0;
1377
1378 while (*fmt) {
1379 if (*fmt == '%') {
1380 fmt++;
1381 bit64 = 0;
1382 fill = 0;
1383 /* Check the format specifier */
1384 loop:
1385 switch (*fmt) {
1386 case 'c':
1387 c = (char)va_arg(*args, int32_t);
1388 *s++ = c;
1389 if (s >= smax) {
1390 s[-1] = 0;
1391 return;
1392 }
1393 break;
1394 case 'i': /* Specifiers i and d do the same thing. */
1395 case 'd':
1396 if (bit64) {
1397 *s = 0;
1398 return;
1399 }
1400
1401 num = va_arg(*args, int32_t);
1402
1403 if (num < 0) {
1404 *s++ = '-';
1405 if (s >= smax) {
1406 s[-1] = 0;
1407 return;
1408 }
1409 unum = (unsigned long int)-num;
1410 } else {
1411 unum = (unsigned long int)num;
1412 }
1413
1414 cli_val2str(&s, smax, unum, 10, fill);
1415 if (s >= smax) {
1416 s[-1] = 0;
1417 return;
1418 }
1419 break;
1420 case 's':
1421 str = va_arg(*args, char *);
1422 while (*str) {
1423 *s++ = *str++;
1424 if (s >= smax) {
1425 s[-1] = 0;
1426 return;
1427 }
1428 }
1429 break;
1430 case 'x': /* All hex prints use uppercase hex digits. */
1431 case 'X':
1432 if (bit64) {
1433 unum = va_arg(*args, uint64_t);
1434 most_significant = (uint32_t)(unum >> 32);
1435 if (most_significant) {
1436 cli_val2str(
1437 &s,
1438 smax,
1439 most_significant,
1440 16,
1441 (fill >= 8) ? fill - 8 : 0);
1442 if (s >= smax) {
1443 s[-1] = 0;
1444 return;
1445 }
1446 cli_val2str(&s, smax, unum, 16, 8);
1447 if (s >= smax) {
1448 s[-1] = 0;
1449 return;
1450 }
1451 } else {
1452 cli_val2str(&s, smax, unum, 16, fill);
1453 if (s >= smax) {
1454 s[-1] = 0;
1455 return;
1456 }
1457 }
1458 } else {
1459 unum = va_arg(*args, uint32_t);
1460 cli_val2str(&s, smax, unum, 16, fill);
1461 if (s >= smax) {
1462 s[-1] = 0;
1463 return;
1464 }
1465 }
1466 break;
1467 case 'l':
1468 bit64 = 1;
1469 fmt++;
1470 goto loop;
1471 case 'u':
1472 if (bit64) {
1473 *s = 0;
1474 return;
1475 }
1476
1477 unum = va_arg(*args, uint32_t);
1478
1479 cli_val2str(&s, smax, unum, 10, fill);
1480 if (s >= smax) {
1481 s[-1] = 0;
1482 return;
1483 }
1484 break;
1485 case '0':
1486 fmt++;
1487 /* Make sure we have a number for fill length. */
1488 if (((*fmt) < '0') || ((*fmt) > '9')) {
1489 *s = 0;
1490 return;
1491 }
1492 fill = strtoul((char *)fmt, (char **)&fmt, 0);
1493 goto loop;
1494 default:
1495 /* Exit on any other format specifier */
1496 *s = 0;
1497 return;
1498 }
1499 fmt++;
1500 continue;
1501 }
1502
1503 *s++ = *fmt++;
1504 if (s == smax) {
1505 s[-1] = 0;
1506 return;
1507 }
1508 }
1509 *s = 0;
1510 }
1511