1 /*
2 * Arm SCP/MCP Software
3 * Copyright (c) 2020-2023, 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 if (string[index] == '\n') {
528 status = fwk_io_putch(fwk_io_stdout, '\r');
529 if (status != FWK_SUCCESS) {
530 return status;
531 }
532 }
533 status = fwk_io_putch(fwk_io_stdout, string[index]);
534 if (status != FWK_SUCCESS) {
535 return status;
536 }
537 }
538 return status;
539 }
540
cli_snprintf(char * s,char * smax,const char * fmt,...)541 void cli_snprintf(char *s, char *smax, const char *fmt, ...)
542 {
543 va_list args;
544
545 va_start(args, fmt);
546 cli_snprintf_arg(s, smax, fmt, &args);
547 va_end(args);
548
549 return;
550 }
551
cli_getline(char * buffer,uint32_t buffer_size,char ** argbuf,uint32_t argbuf_size,uint32_t cursor_position)552 uint32_t cli_getline(
553 char *buffer,
554 uint32_t buffer_size,
555 char **argbuf,
556 uint32_t argbuf_size,
557 uint32_t cursor_position)
558 {
559 char c = 0;
560 uint32_t index = 0;
561 int32_t status = FWK_SUCCESS;
562
563 /* Validate parameters. */
564 if (buffer == NULL ||
565 buffer_size == 0 ||
566 cursor_position >= cli_terminal_width ||
567 argbuf == NULL ||
568 argbuf_size == 0)
569 return FWK_E_PARAM;
570
571 /* Zero out buffer and argbuf. */
572 memset(buffer, 0, buffer_size);
573 memset(argbuf, 0, argbuf_size * sizeof(char **));
574
575 /* Print prompt arrow. */
576 status = cli_print(CLI_PROMPT);
577 if (status != FWK_SUCCESS)
578 return status;
579
580 /* Increment cursor position since we just printed an arrow and space. */
581 cursor_position = (cursor_position + 2) % cli_terminal_width;
582
583 /*
584 * Loop will terminate when the user presses enter or when an error is
585 * generated. This loop will have negligible impact on system performance.
586 */
587 while (1) {
588 /* Grab a character from the UART. */
589 do {
590 status = fwk_io_getch(fwk_io_stdin, &c);
591 } while (status == FWK_PENDING);
592
593 if (status != FWK_SUCCESS)
594 return status;
595
596 /*
597 * Ignore non-printing characters except for a few we care about.
598 * 0x00 - 0x1F are non-printing characters.
599 * 0x7F - 0xFF are non-printing characters.
600 * Carriage return: \r, 0x0D
601 * Newline: \n, 0x0C
602 * Backspace: \b, 0x08
603 * Delete: 0x7F
604 */
605 if ((c <= 0x1F || c >= 0x7F) && c != '\r' && c != '\n' && c != '\b' &&
606 c != 0x7F)
607 continue;
608
609
610 /* If backspace (0x08) or delete (0x7F) character received. */
611 if (c == '\b' || c == 0x7F) {
612 /* Only accept backspace presses if we're not at the beginning of
613 * the string. */
614 if (index != 0) {
615 status = cli_backspace(&cursor_position, cli_terminal_width);
616 if (status != FWK_SUCCESS)
617 return status;
618
619 /* Decrement index and set old last character to null. */
620 index = index - 1;
621 buffer[index] = 0;
622 }
623 continue;
624 }
625
626 /* If newline received. */
627 if (c == '\n' || c == '\r') {
628 /* Making sure the rest of the buffer is zero. */
629 memset(&(buffer[index]), 0, buffer_size - index);
630 status = cli_print("\n");
631 if (status != FWK_SUCCESS)
632 return status;
633 break;
634 }
635
636 /* Echo received character to console. */
637 do {
638 status = fwk_io_putch(fwk_io_stdout, c);
639 } while (status == FWK_PENDING);
640
641 if (status != FWK_SUCCESS)
642 return status;
643
644 cursor_position = (cursor_position + 1) % cli_terminal_width;
645
646 /* Incrementing indices. */
647 buffer[index] = c;
648 index = index + 1;
649 if (index >= buffer_size) {
650 /*
651 * Add null termination in case the user doesn't check return codes
652 * and tries to use the buffer.
653 */
654 buffer[index - 1] = 0;
655 return FWK_E_NOMEM;
656 }
657
658 /* Add new null terminator. */
659 buffer[index] = 0;
660 }
661
662 status = cli_split(argbuf, argbuf_size, buffer, index, " ");
663 if (status != FWK_SUCCESS)
664 return status;
665
666 return FWK_SUCCESS;
667 }
668
669 /*****************************************************************************/
670 /* Private Function Definitions */
671 /*****************************************************************************/
672
cli_main(void const * argument)673 static void cli_main(void const *argument)
674 {
675 int32_t status;
676 uint32_t last_history_index = CLI_CONFIG_HISTORY_LENGTH - 1;
677 uint32_t command_length = 0;
678 bool cli_exit = false;
679
680 /* Thread was started successfully, set state to CLI_RUNNING. */
681 cli_state = CLI_RUNNING;
682
683 /* Initialize command history buffer pointers. */
684 for (uint32_t i = 0; i < CLI_CONFIG_HISTORY_LENGTH; i++)
685 cli_history[i] = &cli_history_buffer[i * CLI_CONFIG_COMMAND_BUF_SIZE];
686
687 /* Loop forever. */
688 while (1) {
689 /* Printing prompt text. */
690 cli_printf(NONE, cli_prompt);
691
692 /* Zero out input buffer. */
693 memset(cli_input_buffer, 0, CLI_CONFIG_COMMAND_BUF_SIZE);
694
695 /* Get command from terminal UART. */
696 status = cli_get_command(
697 cli_input_buffer,
698 CLI_CONFIG_COMMAND_BUF_SIZE - 1,
699 &command_length,
700 cli_history,
701 history_index,
702 CLI_CONFIG_HISTORY_LENGTH,
703 &cli_exit);
704 if (status != FWK_SUCCESS) {
705 cli_error_handler(status);
706 continue;
707 }
708
709 /* Ctrl+d was pressed to exit cli */
710 if (cli_exit) {
711 cli_state = CLI_READY;
712 return;
713 }
714
715 /* Make sure command string is not empty. */
716 if (cli_input_buffer[0] == 0)
717 continue;
718
719
720 /* Update history buffer if command is different than the last one. */
721 if (cli_strncmp(
722 cli_input_buffer,
723 cli_history[last_history_index],
724 CLI_CONFIG_COMMAND_BUF_SIZE) != 0) {
725 strncpy(
726 cli_history[history_index],
727 cli_input_buffer,
728 CLI_CONFIG_COMMAND_BUF_SIZE);
729 history_index = (history_index + 1) % CLI_CONFIG_HISTORY_LENGTH;
730 last_history_index =
731 (last_history_index + 1) % CLI_CONFIG_HISTORY_LENGTH;
732 }
733
734 /* Splitting up command into individual argument strings. */
735 status = cli_split(
736 cli_args,
737 CLI_CONFIG_MAX_NUM_ARGUMENTS,
738 cli_input_buffer,
739 command_length,
740 " ");
741 if (status != FWK_SUCCESS) {
742 cli_error_handler(status);
743 continue;
744 }
745
746 /* If the user didn't type any valid arguments, don't process it. */
747 if (cli_args[0] == 0)
748 continue;
749
750 /* Dispatching command for processing. */
751 status = cli_command_dispatch(cli_args);
752 if (status != FWK_SUCCESS)
753 cli_error_handler(status);
754 }
755 }
756
cli_format(cli_option_et options,char * buffer,uint32_t size)757 uint32_t cli_format(cli_option_et options, char *buffer, uint32_t size)
758 {
759 int32_t status = FWK_SUCCESS;
760 static char tmp_buf[10] = { 0 };
761
762 if (buffer != NULL)
763 /* Add a null terminator before we do anything. */
764 buffer[0] = '\0';
765
766 if (automation_mode)
767 return status;
768
769 /* If no options given, send SGR default sequence to remove formatting. */
770 if (options == NONE) {
771 if (buffer != NULL) {
772 if (size < 5)
773 return FWK_E_NOMEM;
774 memcpy(buffer, "\x1B[0m", 5);
775 buffer = &(buffer[4]);
776 size = size - 4;
777 } else
778 return cli_print("\x1B[0m");
779 }
780 /* Clear terminal window. */
781 if (options & CLEAR_DISPLAY) {
782 if (buffer != NULL) {
783 if (size < 5)
784 return FWK_E_NOMEM;
785 memcpy(buffer, "\x1B[2J", 5);
786 buffer = &(buffer[4]);
787 size = size - 4;
788 } else
789 status = cli_print("\x1B[2J");
790
791 if (status != FWK_SUCCESS)
792 return status;
793 }
794 /* Reset cursor. */
795 if (options & RESET_CURSOR) {
796 if (buffer != NULL) {
797 if (size < 7)
798 return FWK_E_NOMEM;
799
800 memcpy(buffer, "\x1B[0;0f", 7);
801 buffer = &(buffer[6]);
802 size = size - 6;
803 } else
804 status = cli_print("\x1B[0;0f");
805
806 if (status != FWK_SUCCESS)
807 return status;
808 }
809 /* SGR settings. */
810 if (options & CLI_ALL_MASK) {
811 if (buffer != NULL) {
812 if (size < 3)
813 return FWK_E_NOMEM;
814 memcpy(buffer, "\x1B[", 3);
815 buffer = &(buffer[2]);
816 size = size - 2;
817 } else {
818 status = cli_print("\x1B[");
819 if (status != FWK_SUCCESS)
820 return status;
821 }
822 /* Bold/bright. */
823 if (options & BOLD) {
824 if (buffer != NULL) {
825 if (size < 3)
826 return FWK_E_NOMEM;
827
828 memcpy(buffer, ";1", 3);
829 buffer = &(buffer[2]);
830 size = size - 2;
831 } else {
832 status = cli_print(";1");
833 if (status != FWK_SUCCESS)
834 return status;
835 }
836 }
837 /* Underlining. */
838 if (options & UNDERLINE) {
839 if (buffer != NULL) {
840 if (size < 3)
841 return FWK_E_NOMEM;
842 memcpy(buffer, ";4", 3);
843 buffer = &(buffer[2]);
844 size = size - 2;
845 } else {
846 status = cli_print(";4");
847 if (status != FWK_SUCCESS)
848 return status;
849 }
850 }
851 /* Background color. */
852 if (options & CLI_BG_COLOR_MASK) {
853 if (buffer != NULL) {
854 if (size < 4)
855 return FWK_E_NOMEM;
856
857 cli_snprintf(
858 tmp_buf,
859 tmp_buf + 10,
860 (const char *)";%d",
861 (uint16_t)(
862 (options & CLI_BG_COLOR_MASK) >> CLI_BG_COLOR_SHIFT));
863 memcpy(buffer, tmp_buf, 4);
864 buffer = &(buffer[3]);
865 size = size - 3;
866 } else {
867 status = cli_printf(
868 0,
869 ";%d",
870 (uint16_t)(
871 (options & CLI_BG_COLOR_MASK) >> CLI_BG_COLOR_SHIFT));
872 if (status != FWK_SUCCESS)
873 return status;
874 }
875 }
876 /* Foreground color. */
877 if (options & CLI_TEXT_COLOR_MASK) {
878 if (buffer != NULL) {
879 if (size < 4)
880 return FWK_E_NOMEM;
881 cli_snprintf(
882 tmp_buf,
883 tmp_buf + 10,
884 (const char *)";%d",
885 (char)((options & CLI_TEXT_COLOR_MASK) >>
886 CLI_TEXT_COLOR_SHIFT));
887 memcpy(buffer, tmp_buf, 4);
888 buffer = &(buffer[3]);
889 size = size - 3;
890 } else {
891 status = cli_printf(
892 0,
893 ";%d",
894 (char)((options & CLI_TEXT_COLOR_MASK) >>
895 CLI_TEXT_COLOR_SHIFT));
896 if (status != FWK_SUCCESS)
897 return status;
898 }
899 }
900 if (buffer != NULL) {
901 if (size < 2)
902 return FWK_E_NOMEM;
903 memcpy(buffer, "m", 2);
904 } else {
905 status = cli_print("m");
906 if (status != FWK_SUCCESS)
907 return status;
908 }
909 }
910
911 return FWK_SUCCESS;
912 }
913
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)914 static uint32_t cli_get_command(
915 char *buffer,
916 uint32_t buffer_size,
917 uint32_t *command_length,
918 char **history,
919 uint32_t history_index,
920 uint32_t history_size,
921 bool *exit)
922 {
923 int32_t status = FWK_SUCCESS;
924 uint32_t index = 0;
925 uint32_t cursor_index = cli_prompt_size;
926 char c = 0;
927 static char escape[8] = { 0 };
928 uint32_t escape_index = 0;
929 uint32_t history_oldest = history_index;
930 bool flag_escape_sequence = false;
931
932 (void)escape;
933
934 /* Checking parameters. */
935 if (buffer == 0 || buffer_size == 0 || history == 0 ||
936 command_length == 0)
937 return FWK_E_PARAM;
938
939 if (!automation_mode) {
940 /* Getting terminal window size. */
941 /* Saving cursor position. */
942 cli_print("\x1B[s");
943 /* Moving cursor to bottom right position. */
944 cli_print("\x1B[999;999f");
945 /* Requesting new cursor position. */
946 cli_print("\x1B[6n");
947 /* Restoring old cursor position. */
948 cli_print("\x1B[u");
949 }
950
951 while (1) {
952 /* Get character from UART. */
953 do {
954 status = fwk_io_getch(fwk_io_stdin, &c);
955 } while (status == FWK_PENDING);
956
957 if (status != FWK_SUCCESS)
958 return status;
959
960 /* If we received a Ctrl+C press, go to debug mode. */
961 if (c == '\x03') {
962 *command_length = 0;
963 return cli_debug_output();
964 }
965
966 /* If we received a Ctrl+d press, exit cli. */
967 if (c == '\x04') {
968 *exit = true;
969 return FWK_SUCCESS;
970 }
971
972 /* Ignoring non-printing characters except for a few we care about. */
973 if (c < 0x20 && c != '\r' && c != '\n' && c != '\b') {
974 if (c == 0x1B)
975 flag_escape_sequence = true;
976 continue;
977 }
978
979 /* Dealing with escape sequences. */
980 if (flag_escape_sequence == true) {
981 escape[escape_index] = c;
982 escape[escape_index + 1] = 0;
983 escape_index = escape_index + 1;
984
985 /* Escape sequences end with a letter. */
986 if ((c > 0x40 && c < 0x5B) || (c > 0x60 && c < 0x7B)) {
987 flag_escape_sequence = false;
988 escape_index = 0;
989
990 /* Up arrow press. */
991 if (c == 'A') {
992 if (((history_oldest + 1) % history_size) !=
993 history_index) {
994 /* Rewind history index by 1. */
995 if (history_index == 0)
996 history_index = history_size - 1;
997 else
998 history_index = history_index - 1;
999
1000 /* If command entry is empty then stop and restore index
1001 * value. */
1002 if (history[history_index][0] == 0) {
1003 history_index = (history_index + 1) % history_size;
1004 } else {
1005 /* Erasing currently entered command. */
1006 for (; index > 0; index--) {
1007 status = cli_backspace(
1008 &cursor_index, cli_terminal_width);
1009 if (status != FWK_SUCCESS)
1010 return status;
1011 }
1012
1013 /* Copying history command from history buffer. */
1014 strncpy(
1015 buffer, history[history_index], buffer_size);
1016
1017 /* Printing history command to screen. */
1018 while (buffer[index] != 0) {
1019 status =
1020 fwk_io_putch(fwk_io_stdout, buffer[index]);
1021 if (status != FWK_SUCCESS)
1022 return status;
1023 index = index + 1;
1024 cursor_index =
1025 (cursor_index + 1) % cli_terminal_width;
1026 }
1027 }
1028 }
1029 }
1030
1031 /* Down arrow press. */
1032 if (c == 'B') {
1033 if (history_index != history_oldest) {
1034 /* Getting index of history item to load. */
1035 history_index = (history_index + 1) % history_size;
1036
1037 /* Erasing everything in the currently entered command.
1038 */
1039 for (; index > 0; index--) {
1040 status = cli_backspace(
1041 &cursor_index, cli_terminal_width);
1042 if (status != FWK_SUCCESS)
1043 return status;
1044 }
1045
1046 /* If we're back to the current command start fresh and
1047 * zero buffer. */
1048 if (history_index == history_oldest)
1049 memset(buffer, 0, buffer_size);
1050 else {
1051 /* Copying history command from history buffer. */
1052 strncpy(
1053 buffer, history[history_index], buffer_size);
1054
1055 /* Printing history command to screen. */
1056 while (buffer[index] != 0) {
1057 status =
1058 fwk_io_putch(fwk_io_stdout, buffer[index]);
1059 if (status != FWK_SUCCESS)
1060 return status;
1061
1062 index = index + 1;
1063 cursor_index =
1064 (cursor_index + 1) % cli_terminal_width;
1065 }
1066 }
1067 }
1068 }
1069
1070 /* Handling cursor position response sequence. */
1071 if (c == 'R') {
1072 uint32_t i = 0;
1073 for (i = 0; escape[i] != '['; i++)
1074 ;
1075 i++;
1076 cli_terminal_height = strtoul(&escape[i], NULL, 0);
1077 for (; escape[i] != ';'; i++)
1078 ;
1079 i++;
1080 cli_terminal_width = strtoul(&escape[i], NULL, 0);
1081 }
1082 }
1083 continue;
1084 }
1085
1086 /* If backspace (0x08) or delete (0x7F) character received. */
1087 if (c == 0x08 || c == 0x7F) {
1088 /* Only accept backspace presses if we're not at the beginning of
1089 * the string. */
1090 if (index != 0) {
1091 status = cli_backspace(&cursor_index, cli_terminal_width);
1092 if (status != FWK_SUCCESS)
1093 return status;
1094 index = index - 1;
1095 buffer[index] = 0;
1096 }
1097 continue;
1098 }
1099
1100 /* If newline received. */
1101 if (c == '\n' || c == '\r') {
1102 /* Making sure the rest of the buffer is zero. */
1103 memset(&(buffer[index]), 0, buffer_size - index);
1104 *command_length = index;
1105 return cli_print("\n");
1106 }
1107
1108 /* Printing received character to console. */
1109 status = fwk_io_putch(fwk_io_stdout, c);
1110 if (status != FWK_SUCCESS)
1111 return status;
1112
1113 /* Incrementing indices. */
1114 buffer[index] = c;
1115 index = index + 1;
1116 if (index >= buffer_size)
1117 return FWK_E_NOMEM;
1118 buffer[index] = 0;
1119 cursor_index = (cursor_index + 1) % cli_terminal_width;
1120 }
1121 }
1122
cli_backspace(uint32_t * cursor,uint32_t width)1123 static uint32_t cli_backspace(uint32_t *cursor, uint32_t width)
1124 {
1125 uint32_t status = FWK_SUCCESS;
1126
1127 /* If cursor is at the first position of a line. */
1128 if (*cursor == 0) {
1129 status = cli_printf(0, "\x1B[A\x1B[%dC ", width - 1);
1130 if (status != FWK_SUCCESS)
1131 return status;
1132 *cursor = width - 1;
1133 } else {
1134 /* For compatibility, print back, space, back. */
1135 status = cli_print("\x1B[D \x1B[D");
1136 if (status != FWK_SUCCESS)
1137 return status;
1138 *cursor = *cursor - 1;
1139 }
1140
1141 return FWK_SUCCESS;
1142 }
1143
cli_split(char ** argbuf,uint32_t argbuf_size,char * command,uint32_t command_length,const char * whitespace)1144 static uint32_t cli_split(
1145 char **argbuf,
1146 uint32_t argbuf_size,
1147 char *command,
1148 uint32_t command_length,
1149 const char *whitespace)
1150 {
1151 uint32_t index = 0;
1152 uint32_t argbuf_ctr = 0;
1153 bool flag_paren = false;
1154 bool flag_last_was_whitespace = true;
1155
1156 /* Checking pointers. */
1157 if (argbuf == 0 || command == 0 || argbuf_size == 0 || whitespace == 0)
1158 return FWK_E_PARAM;
1159
1160 argbuf[0] = 0;
1161
1162 for (index = 0; index < command_length; index++) {
1163 /* If whitespace is encountered outside of parenthesis, change it to
1164 * null and set flag. */
1165 if (strchr(whitespace, command[index]) != 0 && flag_paren == false) {
1166 command[index] = 0;
1167 flag_last_was_whitespace = true;
1168 continue;
1169 }
1170
1171 /* Handle parenthesis. */
1172 if (command[index] == '\"') {
1173 /* If we've reached the end of an arg in parenthesis, reset flag. */
1174 if (flag_paren == true) {
1175 flag_paren = false;
1176 command[index] = 0;
1177 flag_last_was_whitespace = true;
1178 continue;
1179 }
1180
1181 /* If we receive an opening parenthesis preceded by a whitespace
1182 * character, mark null and enter parenthesis processing. */
1183 if (flag_paren == false && flag_last_was_whitespace == true) {
1184 flag_paren = true;
1185 command[index] = 0;
1186 continue;
1187 }
1188 }
1189
1190 /* First regular character after whitespace encountered. */
1191 if ((strchr(whitespace, command[index]) == 0 || flag_paren == true) &&
1192 flag_last_was_whitespace == true) {
1193 flag_last_was_whitespace = false;
1194 if (argbuf_ctr + 1 >= argbuf_size)
1195 return FWK_E_NOMEM;
1196 argbuf[argbuf_ctr] = &command[index];
1197 argbuf[argbuf_ctr + 1] = 0;
1198 argbuf_ctr = argbuf_ctr + 1;
1199 }
1200 }
1201
1202 /* If the user forgot to close their parenthesis, return an error. */
1203 if (flag_paren == true)
1204 return FWK_E_PARAM;
1205
1206 return FWK_SUCCESS;
1207 }
1208
cli_command_dispatch(char ** args)1209 static uint32_t cli_command_dispatch(char **args)
1210 {
1211 uint32_t index;
1212 uint32_t status;
1213 uint32_t num_args = 0;
1214 struct fwk_slist *node = NULL;
1215 struct command_ctx *cc = NULL;
1216 bool command_found;
1217
1218 /* Checking pointer. */
1219 if (args == 0)
1220 return FWK_E_PARAM;
1221
1222 /* Special case command: help. */
1223 if (cli_strncmp(args[0], "help", 5) == 0) {
1224 FWK_LIST_FOR_EACH(&cli_commands_list, node,
1225 struct command_ctx, list_node, cc) {
1226 cli_printf(NONE, "%s\n", cc->cmd.command);
1227 cli_print(cc->cmd.help);
1228 cli_print("\n");
1229 }
1230
1231 cli_printf(NONE, "help\n");
1232 cli_print(" Displays this information.\n");
1233 cli_printf(NONE, "Ctrl+C\n");
1234 cli_print(" Displays debug output from running threads.\n");
1235 cli_printf(NONE, "Ctrl+d\n");
1236 cli_print(" Exit the CLI.\n");
1237 return FWK_SUCCESS;
1238 }
1239
1240 if (cli_strncmp(args[0], "AUTO", 4) == 0) {
1241 cli_printf(NONE, "AUTO Mode ON\n");
1242 automation_mode = 1;
1243 return FWK_SUCCESS;
1244 }
1245
1246 if (cli_strncmp(args[0], "OTUA", 4) == 0) {
1247 cli_printf(NONE, "AUTO Mode OFF\n");
1248 automation_mode = 0;
1249 return FWK_SUCCESS;
1250 }
1251
1252 /* Searching for command handler. */
1253 /* Using strcmp here because each entry in args is guaranteed to be null
1254 * terminated by cli_split. */
1255 command_found = false;
1256 FWK_LIST_FOR_EACH(&cli_commands_list, node,
1257 struct command_ctx, list_node, cc) {
1258 if (strcmp(args[0], cc->cmd.command) == 0) {
1259 command_found = true;
1260 break;
1261 }
1262 }
1263 if (!command_found)
1264 return FWK_E_SUPPORT;
1265
1266 /* Handler found, if -h or --help is given just print it's help string and
1267 * return. */
1268 if (cc->cmd.ignore_help_flag == false) {
1269 for (index = 1;
1270 (args[index] != 0) && (cli_strncmp(args[index], "-h", 3) != 0) &&
1271 (cli_strncmp(args[index], "--help", 7) != 0);
1272 index++)
1273 ;
1274
1275 if ((args[index] != 0 || cc->cmd.handler == 0) &&
1276 cc->cmd.help != 0) {
1277 status = cli_print(cc->cmd.help);
1278 if (status != FWK_SUCCESS)
1279 return status;
1280
1281 /* Print a newline since help strings shouldn't have newlines at the
1282 * end. */
1283 cli_print("\n");
1284 return status;
1285 }
1286 }
1287
1288 /* Counting arguments. */
1289 for (num_args = 0; args[num_args] != 0; num_args++)
1290 ;
1291
1292 /* Calling command handler. */
1293 /* args is incremented so the command just gets the arguments and not the
1294 * name of itself. */
1295 if (cc->cmd.handler != 0)
1296 return cc->cmd.handler(num_args, args);
1297 else
1298 return FWK_E_PARAM;
1299 }
1300
cli_debug_output(void)1301 static uint32_t cli_debug_output(void)
1302 {
1303 uint32_t fifo_status;
1304 char c = 0;
1305
1306 cli_printf(
1307 0,
1308 "\nNow showing debug console output, to return to the command line, "
1309 "press Ctrl+C.\n");
1310 while (1) {
1311 /* Looking for Ctrl+C press. */
1312 if (fwk_io_getch(fwk_io_stdin, &c) == FWK_SUCCESS) {
1313 if (c == '\x03') {
1314 cli_printf(
1315 0,
1316 "\nNow showing command line, to return to debug output, "
1317 "press Ctrl+C.\n");
1318 return FWK_SUCCESS;
1319 }
1320 }
1321
1322 /* Read from print FIFO. */
1323 fifo_status = fifo_get(&cli_print_fifo, &c);
1324 if (fifo_status == FWK_SUCCESS) {
1325 overflow = false;
1326 fwk_io_putch(fwk_io_stdout, c);
1327 } else
1328 /* If no characters are available, let other stuff run. */
1329 cli_platform_delay_ms(0);
1330 }
1331 }
1332
cli_error_handler(uint32_t status)1333 static void cli_error_handler(uint32_t status)
1334 {
1335 if (status == FWK_SUCCESS)
1336 return;
1337
1338 cli_printf(NONE, "CONSOLE ERROR: %s\n", fwk_status_str(status));
1339 }
1340
cli_val2str(char ** outstr,char * smax,uint32_t value,uint32_t base,int32_t fill)1341 static void cli_val2str(
1342 char **outstr,
1343 char *smax,
1344 uint32_t value,
1345 uint32_t base,
1346 int32_t fill)
1347 {
1348 /* Just need enough space to store 64 bit decimal integer */
1349 unsigned char str[20] = { 0 };
1350 int i = 0;
1351
1352 do {
1353 str[i++] = "0123456789ABCDEF"[value % base];
1354 } while (value /= base);
1355
1356 while (--fill >= i) {
1357 **outstr = '0';
1358 *outstr = *outstr + 1;
1359 if (*outstr >= smax)
1360 return;
1361 }
1362
1363 while (--i >= 0) {
1364 **outstr = str[i];
1365 *outstr = *outstr + 1;
1366 if (*outstr >= smax)
1367 return;
1368 }
1369 }
1370
cli_snprintf_arg(char * s,char * smax,const char * fmt,va_list * args)1371 static void cli_snprintf_arg(
1372 char *s,
1373 char *smax,
1374 const char *fmt,
1375 va_list *args)
1376 {
1377 int bit64;
1378 int fill;
1379 int64_t num = 0;
1380 uint64_t unum = 0;
1381 char *str = NULL;
1382 uint32_t most_significant = 0;
1383 char c = 0;
1384
1385 while (*fmt) {
1386 if (*fmt == '%') {
1387 fmt++;
1388 bit64 = 0;
1389 fill = 0;
1390 /* Check the format specifier */
1391 loop:
1392 switch (*fmt) {
1393 case 'c':
1394 c = (char)va_arg(*args, int32_t);
1395 *s++ = c;
1396 if (s >= smax) {
1397 s[-1] = 0;
1398 return;
1399 }
1400 break;
1401 case 'i': /* Specifiers i and d do the same thing. */
1402 case 'd':
1403 if (bit64) {
1404 *s = 0;
1405 return;
1406 }
1407
1408 num = va_arg(*args, int32_t);
1409
1410 if (num < 0) {
1411 *s++ = '-';
1412 if (s >= smax) {
1413 s[-1] = 0;
1414 return;
1415 }
1416 unum = (unsigned long int)-num;
1417 } else {
1418 unum = (unsigned long int)num;
1419 }
1420
1421 cli_val2str(&s, smax, unum, 10, fill);
1422 if (s >= smax) {
1423 s[-1] = 0;
1424 return;
1425 }
1426 break;
1427 case 's':
1428 str = va_arg(*args, char *);
1429 while (*str) {
1430 *s++ = *str++;
1431 if (s >= smax) {
1432 s[-1] = 0;
1433 return;
1434 }
1435 }
1436 break;
1437 case 'x': /* All hex prints use uppercase hex digits. */
1438 case 'X':
1439 if (bit64) {
1440 unum = va_arg(*args, uint64_t);
1441 most_significant = (uint32_t)(unum >> 32);
1442 if (most_significant) {
1443 cli_val2str(
1444 &s,
1445 smax,
1446 most_significant,
1447 16,
1448 (fill >= 8) ? fill - 8 : 0);
1449 if (s >= smax) {
1450 s[-1] = 0;
1451 return;
1452 }
1453 cli_val2str(&s, smax, unum, 16, 8);
1454 if (s >= smax) {
1455 s[-1] = 0;
1456 return;
1457 }
1458 } else {
1459 cli_val2str(&s, smax, unum, 16, fill);
1460 if (s >= smax) {
1461 s[-1] = 0;
1462 return;
1463 }
1464 }
1465 } else {
1466 unum = va_arg(*args, uint32_t);
1467 cli_val2str(&s, smax, unum, 16, fill);
1468 if (s >= smax) {
1469 s[-1] = 0;
1470 return;
1471 }
1472 }
1473 break;
1474 case 'l':
1475 bit64 = 1;
1476 fmt++;
1477 goto loop;
1478 case 'u':
1479 if (bit64) {
1480 *s = 0;
1481 return;
1482 }
1483
1484 unum = va_arg(*args, uint32_t);
1485
1486 cli_val2str(&s, smax, unum, 10, fill);
1487 if (s >= smax) {
1488 s[-1] = 0;
1489 return;
1490 }
1491 break;
1492 case '0':
1493 fmt++;
1494 /* Make sure we have a number for fill length. */
1495 if (((*fmt) < '0') || ((*fmt) > '9')) {
1496 *s = 0;
1497 return;
1498 }
1499 fill = strtoul((char *)fmt, (char **)&fmt, 0);
1500 goto loop;
1501 default:
1502 /* Exit on any other format specifier */
1503 *s = 0;
1504 return;
1505 }
1506 fmt++;
1507 continue;
1508 }
1509
1510 *s++ = *fmt++;
1511 if (s == smax) {
1512 s[-1] = 0;
1513 return;
1514 }
1515 }
1516 *s = 0;
1517 }
1518