| /* Copyright 2012 The Chromium OS Authors. All rights reserved. |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| /* Console module for Chrome EC */ |
| |
| #include "clock.h" |
| #include "console.h" |
| #ifdef CONFIG_EXPERIMENTAL_CONSOLE |
| #include "crc8.h" |
| #endif /* defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| #include "link_defs.h" |
| #include "system.h" |
| #include "task.h" |
| #include "uart.h" |
| #include "usb_console.h" |
| #include "util.h" |
| |
| #define MAX_ARGS_PER_COMMAND 10 |
| |
| #define PROMPT "> " |
| |
| #ifdef CONFIG_EXPERIMENTAL_CONSOLE |
| #define EC_SYN 0xEC |
| #define EC_ACK 0xC0 |
| #endif /* defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| /* ASCII control character; for example, CTRL('C') = ^C */ |
| #define CTRL(c) ((c) - '@') |
| |
| #ifdef CONFIG_CONSOLE_HISTORY |
| /* History buffers */ |
| static char history[CONFIG_CONSOLE_HISTORY][CONFIG_CONSOLE_INPUT_LINE_SIZE]; |
| static int history_next, history_pos; |
| #endif |
| |
| /* Current console command line */ |
| static char input_buf[CONFIG_CONSOLE_INPUT_LINE_SIZE]; |
| |
| /* Length of current line */ |
| static int input_len; |
| |
| /* Cursor position in current line */ |
| static int input_pos; |
| |
| /* Was last received character a carriage return? */ |
| static int last_rx_was_cr; |
| |
| #ifndef CONFIG_EXPERIMENTAL_CONSOLE |
| /* State of input escape code */ |
| static enum { |
| ESC_OUTSIDE, /* Not in escape code */ |
| ESC_START, /* Got ESC */ |
| ESC_BAD, /* Bad escape sequence */ |
| ESC_BRACKET, /* Got ESC [ */ |
| ESC_BRACKET_1, /* Got ESC [ 1 */ |
| ESC_BRACKET_3, /* Got ESC [ 3 */ |
| ESC_O, /* Got ESC O */ |
| } esc_state; |
| #endif /* !defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| /* Extended key code values, from multi-byte escape sequences */ |
| enum extended_key_code { |
| KEY_UP_ARROW = 0x100, |
| KEY_DOWN_ARROW, |
| KEY_RIGHT_ARROW, |
| KEY_LEFT_ARROW, |
| KEY_END, |
| KEY_HOME, |
| KEY_DEL |
| }; |
| |
| /** |
| * Split a line of input into words. |
| * |
| * @param input Input line; modified to add nulls after each word. |
| * @param argc Destination for number of words. |
| * @param argv Destination array for pointers to words; must be at |
| * least MAX_ARGS_PER_COMMAND entries long. |
| * |
| * @return EC_SUCCESS. If more than MAX_ARGS_PER_COMMAND words are found, |
| * discards the excess and returns EC_ERROR_OVERFLOW. |
| */ |
| static int split_words(char *input, int *argc, char **argv) |
| { |
| char *c; |
| int in_word = 0; |
| int in_line = 1; |
| |
| /* Parse input into words */ |
| *argc = 0; |
| for (c = input; in_line; c++) { |
| if (!*c) |
| in_line = 0; |
| if (isspace(*c) || !*c) { |
| if (in_word) { |
| /* Ending a word */ |
| *c = '\0'; |
| ++*argc; |
| in_word = 0; |
| } |
| } else if (*c == '#') { |
| /* Comments start with hash and go to end of line */ |
| break; |
| } else if (!in_word) { |
| /* Starting a new word */ |
| if (*argc >= MAX_ARGS_PER_COMMAND) |
| return EC_ERROR_OVERFLOW; |
| |
| argv[*argc] = c; |
| in_word = 1; |
| } |
| } |
| return EC_SUCCESS; |
| } |
| |
| /** |
| * Find a command by name. |
| * |
| * Allows partial matches, as long as the partial match is unique to one |
| * command. So "foo" will match "foobar" as long as there isn't also a |
| * command "food". |
| * |
| * @param name Command name to find. |
| * |
| * @return A pointer to the command structure, or NULL if no match found. |
| */ |
| static const struct console_command *find_command(char *name) |
| { |
| const struct console_command *cmd, *match = NULL; |
| int match_length = strlen(name); |
| |
| for (cmd = __cmds; cmd < __cmds_end; cmd++) { |
| if (!strncasecmp(name, cmd->name, match_length)) { |
| if (match) |
| return NULL; |
| /* |
| * Check if 'cmd->name' is of the same length as |
| * 'name'. If yes, then we have a full match. |
| */ |
| if (cmd->name[match_length] == '\0') |
| return cmd; |
| match = cmd; |
| } |
| } |
| |
| return match; |
| } |
| |
| |
| static const char *const errmsgs[] = { |
| "OK", |
| "Unknown error", |
| "Unimplemented", |
| "Overflow", |
| "Timeout", |
| "Invalid argument", |
| "Busy", |
| "Access Denied", |
| "Not Powered", |
| "Not Calibrated", |
| }; |
| |
| /** |
| * Handle a line of input containing a single command. |
| * |
| * @param input Input buffer; modified during parsing. |
| * |
| * @return EC_SUCCESS, or non-zero if error. |
| */ |
| static int handle_command(char *input) |
| { |
| const struct console_command *cmd; |
| char *argv[MAX_ARGS_PER_COMMAND]; |
| int argc = 0; |
| int rv; |
| #ifdef CONFIG_EXPERIMENTAL_CONSOLE |
| char *e = NULL; |
| int i = 0; |
| int j = 0; |
| int command_len = 0; |
| uint8_t packed_crc8 = 0; |
| |
| /* Need to perform some checking to see if our command is intact. */ |
| |
| /* There's nothing to do if the buffer is empty. */ |
| if (!input_len) |
| return EC_SUCCESS; |
| |
| /* |
| * Scan the first two characters in the command string looking for |
| * ampersands. We need at least one to continue. |
| */ |
| if ((input_len < 2) || (input[0] != '&' && input[1] != '&')) |
| goto command_has_error; |
| |
| /* |
| * Okay, we've found at least one. We need to see if we actually got |
| * 2 ampersands in order to adjust our position properly. |
| */ |
| i = input[1] == '&' ? 2 : 1; |
| |
| /* Next, there should be 4 hex digits: XXYY + '&' */ |
| if (i+5 > input_len) |
| goto command_has_error; |
| /* Replace the '&' with null so we can call strtoi(). */ |
| input[i+4] = 0; |
| j = strtoi(input + i, &e, 16); |
| if (*e) |
| goto command_has_error; |
| /* command length = XX, CRC8 = YY */ |
| command_len = j >> 8; |
| packed_crc8 = (uint8_t)j; |
| i += 5; |
| |
| /* Lastly, verify the CRC8 of the command. */ |
| if (i+command_len > input_len) |
| goto command_has_error; |
| if (packed_crc8 != crc8(&input[i], command_len)) { |
| command_has_error: |
| /* Send back the error string. */ |
| ccprintf("&&EE\n"); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| /* Split input into words. Ignore words past our limit. */ |
| split_words(&input[i], &argc, argv); |
| #else |
| split_words(input, &argc, argv); |
| #endif /* defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| /* If no command, nothing to do */ |
| if (!argc) |
| return EC_SUCCESS; |
| |
| cmd = find_command(argv[0]); |
| if (!cmd) { |
| ccprintf("Command '%s' not found or ambiguous.\n", argv[0]); |
| return EC_ERROR_UNKNOWN; |
| } |
| |
| #ifdef CONFIG_RESTRICTED_CONSOLE_COMMANDS |
| if (console_is_restricted() && cmd->flags & CMD_FLAG_RESTRICTED) |
| rv = EC_ERROR_ACCESS_DENIED; |
| else |
| #endif |
| rv = cmd->handler(argc, argv); |
| if (rv == EC_SUCCESS) |
| return rv; |
| |
| /* Print more info for errors */ |
| if (rv < ARRAY_SIZE(errmsgs)) |
| ccprintf("%s\n", errmsgs[rv]); |
| else if (rv >= EC_ERROR_PARAM1 && rv < EC_ERROR_PARAM_COUNT) |
| ccprintf("Parameter %d invalid\n", rv - EC_ERROR_PARAM1 + 1); |
| else if (rv == EC_ERROR_PARAM_COUNT) |
| ccputs("Wrong number of params\n"); |
| else if (rv != EC_SUCCESS) |
| ccprintf("Command returned error %d\n", rv); |
| |
| #ifdef CONFIG_CONSOLE_CMDHELP |
| if (cmd->argdesc) |
| ccprintf("Usage: %s %s\n", cmd->name, cmd->argdesc); |
| #endif |
| return rv; |
| } |
| |
| static void console_init(void) |
| { |
| *input_buf = '\0'; |
| #ifdef CONFIG_EXPERIMENTAL_CONSOLE |
| ccprintf("Enhanced Console is enabled (v1.0.0); type HELP for help.\n"); |
| #else |
| ccprintf("Console is enabled; type HELP for help.\n"); |
| #endif /* defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| ccputs(PROMPT); |
| } |
| |
| static int console_putc(int c) |
| { |
| int rv1 = uart_putc(c); |
| int rv2 = usb_putc(c); |
| |
| return rv1 == EC_SUCCESS ? rv2 : rv1; |
| } |
| |
| #ifndef CONFIG_EXPERIMENTAL_CONSOLE |
| static void move_cursor_right(void) |
| { |
| if (input_pos == input_len) |
| return; |
| |
| ccputs("\x1b[1C"); |
| input_pos++; |
| } |
| |
| static void move_cursor_end(void) |
| { |
| if (input_pos == input_len) |
| return; |
| |
| ccprintf("\x1b[%dC", input_len - input_pos); |
| input_pos = input_len; |
| } |
| |
| static void move_cursor_left(void) |
| { |
| if (input_pos == 0) |
| return; |
| |
| ccputs("\x1b[1D"); |
| input_pos--; |
| } |
| |
| static void move_cursor_begin(void) |
| { |
| if (input_pos == 0) |
| return; |
| |
| ccprintf("\x1b[%dD", input_pos); |
| input_pos = 0; |
| } |
| #endif /* !defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| static void repeat_char(char c, int cnt) |
| { |
| while (cnt--) |
| console_putc(c); |
| } |
| |
| #ifdef CONFIG_CONSOLE_HISTORY |
| |
| /** |
| * Load input history |
| * |
| * @param idx History index to load |
| */ |
| static void load_history(int idx) |
| { |
| /* Copy history */ |
| strzcpy(input_buf, history[idx], CONFIG_CONSOLE_INPUT_LINE_SIZE); |
| |
| /* Print history */ |
| move_cursor_begin(); |
| ccputs(input_buf); |
| |
| /* Clear everything past end of history */ |
| input_pos = strlen(input_buf); |
| if (input_len > input_pos) { |
| repeat_char(' ', input_len - input_pos); |
| repeat_char('\b', input_len - input_pos); |
| } |
| input_len = input_pos; |
| } |
| |
| /** |
| * Save line to the next history slot |
| */ |
| static void save_history(void) |
| { |
| strzcpy(history[history_next], input_buf, |
| CONFIG_CONSOLE_INPUT_LINE_SIZE); |
| } |
| |
| #endif /* CONFIG_CONSOLE_HISTORY */ |
| |
| #ifndef CONFIG_EXPERIMENTAL_CONSOLE |
| static void handle_backspace(void) |
| { |
| if (!input_pos) |
| return; /* Already at beginning of line */ |
| |
| /* Move cursor back */ |
| console_putc('\b'); |
| |
| /* Print and move anything following the cursor position */ |
| if (input_pos != input_len) { |
| ccputs(input_buf + input_pos); |
| memmove(input_buf + input_pos - 1, |
| input_buf + input_pos, |
| input_len - input_pos + 1); |
| } else { |
| input_buf[input_len - 1] = '\0'; |
| } |
| |
| /* Space over last character and move cursor to correct position */ |
| console_putc(' '); |
| repeat_char('\b', input_len - input_pos + 1); |
| |
| input_len--; |
| input_pos--; |
| } |
| |
| /** |
| * Escape code handler |
| * |
| * @param c Next received character. |
| * @return Key code, or -1 if character was eaten |
| */ |
| static int handle_esc(int c) |
| { |
| switch (esc_state) { |
| case ESC_START: |
| if (c == '[') { |
| esc_state = ESC_BRACKET; |
| return -1; |
| } else if (c == 'O') { |
| esc_state = ESC_O; |
| return -1; |
| } |
| break; |
| |
| case ESC_BRACKET: |
| if (c == '1') { |
| esc_state = ESC_BRACKET_1; |
| return -1; |
| } else if (c == '3') { |
| esc_state = ESC_BRACKET_3; |
| return -1; |
| } |
| |
| if (c == 'A') |
| return KEY_UP_ARROW; |
| else if (c == 'B') |
| return KEY_DOWN_ARROW; |
| else if (c == 'C') |
| return KEY_RIGHT_ARROW; |
| else if (c == 'D') |
| return KEY_LEFT_ARROW; |
| break; |
| |
| case ESC_O: |
| if (c == 'F') |
| return KEY_END; |
| break; |
| |
| case ESC_BRACKET_1: |
| if (c == '~') |
| return KEY_HOME; |
| break; |
| |
| case ESC_BRACKET_3: |
| if (c == '~') |
| return KEY_DEL; |
| break; |
| |
| default: |
| break; |
| } |
| |
| /* Check if the escape code is done */ |
| if (isalpha(c) || c == '~') |
| esc_state = ESC_OUTSIDE; |
| else |
| esc_state = ESC_BAD; |
| |
| return -1; |
| } |
| #endif /* !defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| static void console_handle_char(int c) |
| { |
| #ifdef CONFIG_EXPERIMENTAL_CONSOLE |
| /* |
| * If we receive a EC_SYN, we should respond immediately with a EC_ACK. |
| * This handshake lets the interpreter know that this is an enhanced |
| * image. |
| */ |
| if (c == EC_SYN) { |
| console_putc(EC_ACK); |
| return; |
| } |
| #endif /* defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| /* Translate CR and CRLF to LF (newline) */ |
| if (c == '\r') { |
| last_rx_was_cr = 1; |
| c = '\n'; |
| } else if (c == '\n' && last_rx_was_cr) { |
| last_rx_was_cr = 0; |
| return; |
| } else { |
| last_rx_was_cr = 0; |
| } |
| |
| #ifndef CONFIG_EXPERIMENTAL_CONSOLE |
| /* Handle terminal escape sequences (ESC [ ...) */ |
| if (c == 0x1B) { |
| esc_state = ESC_START; |
| return; |
| } else if (esc_state) { |
| c = handle_esc(c); |
| if (c != -1) |
| esc_state = ESC_OUTSIDE; |
| } |
| #endif /* !defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| switch (c) { |
| #ifndef CONFIG_EXPERIMENTAL_CONSOLE |
| case KEY_DEL: |
| if (input_pos == input_len) |
| break; /* Already at end */ |
| |
| move_cursor_right(); |
| |
| /* Drop through to backspace handling */ |
| case '\b': |
| case 0x7f: |
| handle_backspace(); |
| break; |
| #endif /* !defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| case '\n': |
| #ifndef CONFIG_EXPERIMENTAL_CONSOLE |
| /* Terminate this line */ |
| console_putc('\n'); |
| #endif /* !defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| #ifdef CONFIG_CONSOLE_HISTORY |
| /* Save command in history buffer */ |
| if (input_len) { |
| save_history(); |
| history_next = (history_next + 1) % |
| CONFIG_CONSOLE_HISTORY; |
| history_pos = history_next; |
| } |
| #endif |
| |
| /* Handle command */ |
| handle_command(input_buf); |
| |
| /* Start new line */ |
| input_pos = input_len = 0; |
| input_buf[0] = '\0'; |
| |
| #ifndef CONFIG_EXPERIMENTAL_CONSOLE |
| /* Reprint prompt */ |
| ccputs(PROMPT); |
| #endif /* !defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| break; |
| |
| #ifndef CONFIG_EXPERIMENTAL_CONSOLE |
| case CTRL('A'): |
| case KEY_HOME: |
| move_cursor_begin(); |
| break; |
| |
| case CTRL('B'): |
| case KEY_LEFT_ARROW: |
| move_cursor_left(); |
| break; |
| |
| case CTRL('E'): |
| case KEY_END: |
| move_cursor_end(); |
| break; |
| |
| case CTRL('F'): |
| case KEY_RIGHT_ARROW: |
| move_cursor_right(); |
| break; |
| |
| case CTRL('K'): |
| /* Kill to end of line */ |
| if (input_pos == input_len) |
| break; |
| |
| repeat_char(' ', input_len - input_pos); |
| repeat_char('\b', input_len - input_pos); |
| input_len = input_pos; |
| input_buf[input_len] = '\0'; |
| break; |
| |
| case CTRL('L'): |
| /* Reprint current */ |
| ccputs("\x0c" PROMPT); |
| ccputs(input_buf); |
| repeat_char('\b', input_len - input_pos); |
| break; |
| |
| #ifdef CONFIG_CONSOLE_HISTORY |
| |
| case CTRL('P'): |
| case KEY_UP_ARROW: |
| /* History previous */ |
| if (history_pos == history_next) |
| save_history(); |
| |
| if (--history_pos < 0) |
| history_pos = CONFIG_CONSOLE_HISTORY - 1; |
| |
| load_history(history_pos); |
| break; |
| |
| case CTRL('N'): |
| case KEY_DOWN_ARROW: |
| /* History next */ |
| if (history_pos == history_next) |
| save_history(); |
| |
| if (++history_pos >= CONFIG_CONSOLE_HISTORY) |
| history_pos = 0; |
| |
| load_history(history_pos); |
| break; |
| |
| #endif /* CONFIG_CONSOLE_HISTORY */ |
| #endif /* !defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| default: |
| /* Ignore non-printing characters */ |
| if (!isprint(c)) |
| break; |
| |
| #ifndef CONFIG_EXPERIMENTAL_CONSOLE |
| /* Ignore if line is full (leaving room for terminating null) */ |
| if (input_len >= sizeof(input_buf) - 1) |
| break; |
| |
| /* Print character */ |
| console_putc(c); |
| #endif /* !defined(CONFIG_EXPERIMENTAL_CONSOLE) */ |
| |
| /* If not at end of line, print rest of line and move it down */ |
| if (input_pos != input_len) { |
| ccputs(input_buf + input_pos); |
| memmove(input_buf + input_pos + 1, |
| input_buf + input_pos, |
| input_len - input_pos + 1); |
| repeat_char('\b', input_len - input_pos); |
| } |
| |
| /* Add character to buffer and terminate it */ |
| input_buf[input_pos++] = c; |
| input_buf[++input_len] = '\0'; |
| } |
| } |
| |
| void console_has_input(void) |
| { |
| #ifdef CONFIG_LOW_POWER_IDLE |
| /* Notify the clock module that the console is in use. */ |
| clock_refresh_console_in_use(); |
| #endif |
| |
| /* Wake up the console task */ |
| task_wake(TASK_ID_CONSOLE); |
| } |
| |
| void console_task(void *u) |
| { |
| console_init(); |
| |
| while (1) { |
| int c; |
| |
| while (1) { |
| c = uart_getc(); |
| if (c == -1) |
| break; |
| console_handle_char(c); |
| } |
| |
| while (1) { |
| c = usb_getc(); |
| if (c == -1) |
| break; |
| console_handle_char(c); |
| } |
| |
| task_wait_event(-1); /* Wait for more input */ |
| } |
| } |
| |
| /*****************************************************************************/ |
| /* Console commands */ |
| |
| /* Command handler - prints help. */ |
| static int command_help(int argc, char **argv) |
| { |
| const int ncmds = __cmds_end - __cmds; |
| const int cols = 5; /* printing in five columns */ |
| const int rows = (ncmds + cols - 1) / cols; |
| int i, j; |
| |
| #ifdef CONFIG_CONSOLE_CMDHELP |
| if (argc == 2) { |
| const struct console_command *cmd; |
| |
| if (!strcasecmp(argv[1], "list")) { |
| #ifdef CONFIG_CONSOLE_COMMAND_FLAGS |
| ccputs("Command Flags Description\n"); |
| for (i = 0; i < ncmds; i++) { |
| ccprintf(" %-14s %x %s\n", |
| __cmds[i].name, __cmds[i].flags, |
| __cmds[i].help); |
| cflush(); |
| } |
| #else |
| ccputs("Known commands:\n"); |
| for (i = 0; i < ncmds; i++) { |
| ccprintf(" %-15s%s\n", |
| __cmds[i].name, __cmds[i].help); |
| cflush(); |
| } |
| #endif |
| ccputs("HELP CMD = help on CMD.\n"); |
| return EC_SUCCESS; |
| } |
| cmd = find_command(argv[1]); |
| if (!cmd) { |
| ccprintf("Command '%s' not found or ambiguous.\n", |
| argv[1]); |
| return EC_ERROR_UNKNOWN; |
| } |
| ccprintf("Usage: %s %s\n", cmd->name, |
| (cmd->argdesc ? cmd->argdesc : "")); |
| if (cmd->help) |
| ccprintf("%s\n", cmd->help); |
| return EC_SUCCESS; |
| } |
| #endif |
| |
| ccputs("Known commands:\n"); |
| for (i = 0; i < rows; i++) { |
| ccputs(" "); |
| for (j = 0; j < cols; j++) { |
| int index = j * rows + i; |
| if (index >= ncmds) |
| break; |
| #ifdef CONFIG_RESTRICTED_CONSOLE_COMMANDS |
| if (console_is_restricted() && |
| __cmds[index].flags & CMD_FLAG_RESTRICTED) |
| ccprintf("-%-14s", __cmds[index].name); |
| else |
| #endif |
| ccprintf(" %-14s", __cmds[index].name); |
| } |
| ccputs("\n"); |
| cflush(); |
| } |
| |
| #ifdef CONFIG_CONSOLE_CMDHELP |
| ccputs("HELP LIST = more info; "); |
| ccputs("HELP CMD = help on CMD.\n"); |
| #endif |
| |
| return EC_SUCCESS; |
| } |
| DECLARE_SAFE_CONSOLE_COMMAND(help, command_help, |
| "[ list | <name> ]", |
| "Print command help"); |
| |
| #ifdef CONFIG_CONSOLE_HISTORY |
| static int command_history(int argc, char **argv) |
| { |
| int i; |
| |
| for (i = 0; i < CONFIG_CONSOLE_HISTORY; i++) { |
| int idx = (history_next + i) % CONFIG_CONSOLE_HISTORY; |
| if (history[idx][0]) |
| ccprintf("%s\n", history[idx]); |
| } |
| |
| return EC_SUCCESS; |
| } |
| DECLARE_SAFE_CONSOLE_COMMAND(history, command_history, |
| NULL, |
| "Print console history"); |
| #endif |