summaryrefslogtreecommitdiff
path: root/common/console.c
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2013-08-01 14:36:43 -0700
committerChromeBot <chrome-bot@google.com>2013-08-02 17:32:26 -0700
commit889f7bdd3b77151dd5b749974c94a84ae8b2aeb8 (patch)
tree3973fd501c819b66d0504514fd7c6f2aada28b0e /common/console.c
parent078dfabb68be3573185bffdd79ef0f079002ee1e (diff)
downloadchrome-ec-889f7bdd3b77151dd5b749974c94a84ae8b2aeb8.tar.gz
Move input character processing from UART interrupt to console task
Previously, processing of arrow keys and control characters was done in the interrupt handler itself. This increased the impact of UART input on other interrupts and high-priority tasks. It also makes it harder to implement DMA-based UART input on STM32L (in an imminent CL), since the processing affected the circular UART input buffer in-place. This change turns uart_buffering.c back into a dumb I/O buffering module, and puts all the command line editing and history support into console.c. Console history is done via a simple array of input lines instead of a packed circular buffer of characters. This is a little less RAM-efficient, but is easier to implement and read. History depth is controlled via CONFIG_CONSOLE_HISTORY, and is 3 for STM32F and 8 for other platforms. If we really need a greater history depth, we can look into implementing a packed circular buffer again, but this time at task time in console.c. Also added a 'history' command to print the current console history. BUG=chrome-os-partner:20485 BRANCH=none TEST=console_edit unit test passes; 'history' command prints the last commands Change-Id: I142a0be0d67718c58341e4569f4e2908f191d8b0 Signed-off-by: Randall Spangler <rspangler@chromium.org> Reviewed-on: https://gerrit.chromium.org/gerrit/64363 Reviewed-by: Vic Yang <victoryang@chromium.org>
Diffstat (limited to 'common/console.c')
-rw-r--r--common/console.c400
1 files changed, 388 insertions, 12 deletions
diff --git a/common/console.c b/common/console.c
index dd12613f9a..78fd96a7c7 100644
--- a/common/console.c
+++ b/common/console.c
@@ -16,9 +16,49 @@
#define PROMPT "> "
+/* 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;
+
+/* 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;
+
+/* 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.
*
@@ -34,11 +74,14 @@ 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; *c; c++) {
- if (isspace(*c)) {
+ for (c = input; in_line; c++) {
+ if (!*c)
+ in_line = 0;
+ if (isspace(*c) || !*c) {
if (in_word) {
/* Ending a word */
*c = '\0';
@@ -150,16 +193,327 @@ static void console_init(void)
ccputs(PROMPT);
}
-static void console_process(void)
+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)
{
- /*
- * Process all pending console commands. Need to do this all at once
- * since our interrupt may have been triggered multiple times.
- */
- while (uart_peek('\n') >= 0) {
- uart_gets(input_buf, sizeof(input_buf));
+ 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;
+}
+
+static void repeat_char(char c, int cnt)
+{
+ while (cnt--)
+ uart_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 */
+
+static void handle_backspace(void)
+{
+ if (!input_pos)
+ return; /* Already at beginning of line */
+
+ /* Move cursor back */
+ uart_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 */
+ uart_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;
+}
+
+static void console_handle_char(int c)
+{
+ /* 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;
+ }
+
+ /* 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;
+ }
+
+ switch (c) {
+ 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;
+
+ case '\n':
+ /* Terminate this line */
+ uart_puts("\r\n");
+
+#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';
+
+ /* Reprint prompt */
ccputs(PROMPT);
+ break;
+
+ 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 */
+
+ default:
+ /* Ignore non-printing characters */
+ if (!isprint(c))
+ break;
+
+ /* Ignore if line is full (leaving room for terminating null) */
+ if (input_len >= sizeof(input_buf) - 1)
+ break;
+
+ /* Print character */
+ uart_putc(c);
+
+ /* 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';
}
}
@@ -183,9 +537,12 @@ void console_task(void)
console_init();
while (1) {
- console_process();
- /* Wait for the next command message */
- task_wait_event(-1);
+ int c = uart_getc();
+
+ if (c == -1)
+ task_wait_event(-1); /* Wait for more input */
+ else
+ console_handle_char(c);
}
}
@@ -274,3 +631,22 @@ DECLARE_CONSOLE_COMMAND(forceen, command_force_enabled,
"Force enable console",
NULL);
#endif
+
+#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_CONSOLE_COMMAND(history, command_history,
+ NULL,
+ "Print console history",
+ NULL);
+#endif