diff options
Diffstat (limited to 'common/uart_buffering.c')
-rw-r--r-- | common/uart_buffering.c | 665 |
1 files changed, 665 insertions, 0 deletions
diff --git a/common/uart_buffering.c b/common/uart_buffering.c new file mode 100644 index 0000000000..4706ce895d --- /dev/null +++ b/common/uart_buffering.c @@ -0,0 +1,665 @@ +/* Copyright (c) 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. + */ + +/* Common code to do UART buffering and printing */ + +#include <stdarg.h> + +#include "console.h" +#include "task.h" +#include "uart.h" +#include "util.h" + +/* Buffer sizes; should be power of 2 */ +#define TX_BUF_SIZE 512 +#define RX_BUF_SIZE 128 /* suggest larger than 80 to copy&paste script. */ +#define HISTORY_SIZE 8 + +/* The size limit of single command */ +#define RX_LINE_SIZE 80 + +/* Macros to advance in the circular buffers */ +#define TX_BUF_NEXT(i) (((i) + 1) & (TX_BUF_SIZE - 1)) +#define RX_BUF_NEXT(i) (((i) + 1) & (RX_BUF_SIZE - 1)) +#define RX_BUF_PREV(i) (((i) - 1) & (RX_BUF_SIZE - 1)) +#define CMD_HIST_NEXT(i) (((i) + 1) & (HISTORY_SIZE - 1)) +#define CMD_HIST_PREV(i) (((i) - 1) & (HISTORY_SIZE - 1)) + +/* Macro to calculate difference of pointers in the circular receive buffer. */ +#define RX_BUF_DIFF(i, j) (((i) - (j)) & (RX_BUF_SIZE - 1)) + +/* Transmit and receive buffers */ +static volatile char tx_buf[TX_BUF_SIZE]; +static volatile int tx_buf_head; +static volatile int tx_buf_tail; +static volatile char rx_buf[RX_BUF_SIZE]; +static volatile int rx_buf_head; +static volatile int rx_buf_tail; +static volatile char rx_cur_buf[RX_LINE_SIZE]; +static volatile int rx_cur_buf_tail; +static volatile int rx_cur_buf_head; +static volatile int rx_cur_buf_ptr; +static int last_rx_was_cr; +static int in_escape; +static char esc_seq_char; + +/* Command history */ +struct cmd_history_t { + volatile int head; + volatile int tail; +}; +static struct cmd_history_t cmd_history[HISTORY_SIZE]; +static volatile int cmd_history_head; +static volatile int cmd_history_tail; +static volatile int cmd_history_ptr; + +static int console_mode = 1; + +/* TODO: should have an API to set raw mode for the UART. In raw + * mode, we don't do CRLF translation or echo input. */ + + +/* Put a single character into the transmit buffer. Does not enable + * the transmit interrupt; assumes that happens elsewhere. Returns + * zero if the character was transmitted, 1 if it was dropped. */ +static int __tx_char(int c) +{ + int tx_buf_next; + + /* Do newline to CRLF translation */ + if (console_mode && c == '\n' && __tx_char('\r')) + return 1; + + tx_buf_next = TX_BUF_NEXT(tx_buf_head); + if (tx_buf_next == tx_buf_tail) + return 1; + + tx_buf[tx_buf_head] = c; + tx_buf_head = tx_buf_next; + return 0; +} + +static void move_rx_ptr_fwd(void) +{ + if (rx_cur_buf_ptr != rx_cur_buf_head) { + ++rx_cur_buf_ptr; + uart_write_char(0x1B); + uart_write_char('['); + uart_write_char('1'); + uart_write_char('C'); + } +} + +static void move_rx_ptr_bwd(void) +{ + if (rx_cur_buf_ptr != 0) { + --rx_cur_buf_ptr; + uart_write_char(0x1B); + uart_write_char('['); + uart_write_char('1'); + uart_write_char('D'); + } +} + +static void repeat_char(char c, int cnt) +{ + while (cnt--) + uart_write_char(c); +} + +static void handle_backspace(void) +{ + if (rx_cur_buf_ptr != 0) { + /* Move texts after cursor and also update rx buffer. */ + int ptr; + for (ptr = rx_cur_buf_ptr; ptr < rx_cur_buf_head; ++ptr) { + uart_write_char(rx_cur_buf[ptr]); + rx_cur_buf[ptr - 1] = rx_cur_buf[ptr]; + } + + /* Space over last character and move cursor back to correct + * position. + */ + uart_write_char(' '); + repeat_char('\b', ptr - rx_cur_buf_ptr + 1); + + --rx_cur_buf_head; + --rx_cur_buf_ptr; + } + else + /* Cursor moves pass the first character. Move it back. */ + uart_write_char(' '); +} + +static void insert_char(char c) +{ + int ptr; + + /* On overflow, discard input */ + if (rx_cur_buf_head == RX_LINE_SIZE) + return; + + /* Move buffer ptr to the end if 'c' is new line */ + if (c == '\n') + rx_cur_buf_ptr = rx_cur_buf_head; + + /* Move text after cursor. */ + for (ptr = rx_cur_buf_ptr; ptr < rx_cur_buf_head; ++ptr) + uart_write_char(rx_cur_buf[ptr]); + + /* Insert character to rx buffer and move cursor to correct + * position. + */ + repeat_char('\b', ptr - rx_cur_buf_ptr); + for (ptr = rx_cur_buf_head; ptr > rx_cur_buf_ptr; --ptr) + rx_cur_buf[ptr] = rx_cur_buf[ptr - 1]; + rx_cur_buf[rx_cur_buf_ptr] = c; + ++rx_cur_buf_head; + ++rx_cur_buf_ptr; + + /* Insert character directly into rx_buf if not in console mode. */ + if (!console_mode) { + rx_buf[rx_buf_head] = c; + rx_buf_head = RX_BUF_NEXT(rx_buf_head); + if (rx_buf_tail == rx_buf_head) + rx_buf_tail = RX_BUF_NEXT(rx_buf_tail); + } +} + +static int rx_buf_space_available(void) +{ + if (cmd_history_head == cmd_history_tail) + return RX_BUF_SIZE; + return RX_BUF_DIFF(cmd_history[cmd_history_tail].tail, + cmd_history[CMD_HIST_PREV(cmd_history_head)].head); +} + +static void history_save(void) +{ + int ptr; + int tail, head; + int hist_id; + + /* If there is not enough space in rx buffer, discard the oldest + * history. */ + while (rx_buf_space_available() < rx_cur_buf_head) + cmd_history_tail = CMD_HIST_NEXT(cmd_history_tail); + + /* If history buffer is full, discard the oldest one */ + hist_id = cmd_history_head; + cmd_history_head = CMD_HIST_NEXT(cmd_history_head); + if (cmd_history_head == cmd_history_tail) + cmd_history_tail = CMD_HIST_NEXT(cmd_history_tail); + + /* Copy the current command, but we do not save the '\n' */ + if (hist_id == cmd_history_tail) + tail = 0; + else + tail = cmd_history[CMD_HIST_PREV(hist_id)].head + 1; + head = tail; + for (ptr = 0; ptr < rx_cur_buf_head; ++ptr, head = RX_BUF_NEXT(head)) + rx_buf[head] = rx_cur_buf[ptr]; + if (rx_buf[RX_BUF_PREV(head)] == '\n') { + head = RX_BUF_PREV(head); + rx_buf[head] = '\0'; + } + + cmd_history[hist_id].head = head; + cmd_history[hist_id].tail = tail; +} + +static void history_load(int id) +{ + int head = cmd_history[id].head; + int tail = cmd_history[id].tail; + int ptr; + + cmd_history_ptr = id; + + /* Move cursor back to begin of the line. */ + repeat_char('\b', rx_cur_buf_ptr); + + /* Load command and print it. */ + for (ptr = tail, rx_cur_buf_ptr = 0; ptr != head; + ptr = RX_BUF_NEXT(ptr), ++rx_cur_buf_ptr) { + rx_cur_buf[rx_cur_buf_ptr] = rx_buf[ptr]; + uart_write_char(rx_buf[ptr]); + } + + /* If needed, space over the remaining text. */ + if (rx_cur_buf_ptr < rx_cur_buf_head) { + repeat_char(' ', rx_cur_buf_head - rx_cur_buf_ptr); + repeat_char('\b', rx_cur_buf_head - rx_cur_buf_ptr); + } + + rx_cur_buf_head = rx_cur_buf_ptr; +} + +static void history_prev(void) +{ + if (cmd_history_ptr == cmd_history_tail) + return; + + /* Stash the current command if we are not currently using history. + * Prevent loading history if there is no space to stash current + * command. */ + if (cmd_history_ptr == cmd_history_head) { + int last_id = CMD_HIST_PREV(cmd_history_head); + int last_len = RX_BUF_DIFF(cmd_history[last_id].head, + cmd_history[last_id].tail); + if (last_len + rx_cur_buf_head > RX_BUF_SIZE) + return; + + history_save(); + } + + cmd_history_ptr = CMD_HIST_PREV(cmd_history_ptr); + history_load(cmd_history_ptr); +} + +static void history_next(void) +{ + if (cmd_history_ptr == cmd_history_head) + return; + + cmd_history_ptr = CMD_HIST_NEXT(cmd_history_ptr); + history_load(cmd_history_ptr); + + /* Remove the stashed command if we just loaded it. */ + if (cmd_history_ptr == CMD_HIST_PREV(cmd_history_head)) + cmd_history_head = cmd_history_ptr; +} + +/* Helper for UART processing */ +void uart_process(void) +{ + /* Copy input from buffer until RX fifo empty */ + while (uart_rx_available()) { + int c = uart_read_char(); + + /* Handle console mode echoing and translation */ + if (console_mode) { + /* 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; + continue; + } else { + last_rx_was_cr = 0; + } + + /* Handle left and right key, and eat other terminal + * escape sequences (ESC [ ...). + * Would be really cool if we used arrow keys to edit + * command history, but for now it's sufficient just to + * keep them from causing problems. */ + if (c == 0x1B) { + in_escape = 1; + esc_seq_char = c; + continue; + } else if (in_escape) { + if (esc_seq_char == 0x1B && c == '[') + esc_seq_char = '['; + else if (esc_seq_char == '[') { + if (c == 'A') /* Up key */ + history_prev(); + else if (c == 'B') /* Down key */ + history_next(); + else if (c == 'C') /* Right key */ + move_rx_ptr_fwd(); + else if (c == 'D') /* Left key */ + move_rx_ptr_bwd(); + esc_seq_char = 0; + } + else + esc_seq_char = 0; + + if (isalpha(c) || c == '~') { + esc_seq_char = 0; + in_escape = 0; + } + continue; + } + + /* Echo characters directly to the transmit FIFO so we + * don't interfere with the transmit buffer. */ + if (c == '\n') + uart_write_char('\r'); + uart_write_char(c); + + /* Handle backspace if we can */ + if (c == '\b') { + handle_backspace(); + continue; + } + } + + insert_char(c); + + /* Call console callback on newline, if in console mode */ + if (console_mode && c == '\n') + console_has_input(); + } + + /* Copy output from buffer until TX fifo full or output buffer empty */ + while (uart_tx_ready() && (tx_buf_head != tx_buf_tail)) { + uart_write_char(tx_buf[tx_buf_tail]); + tx_buf_tail = TX_BUF_NEXT(tx_buf_tail); + } + + /* If output buffer is empty, disable transmit interrupt */ + if (tx_buf_tail == tx_buf_head) + uart_tx_stop(); +} + +void uart_set_console_mode(int enable) +{ + console_mode = enable; + + if (!enable) + rx_cur_buf_ptr = rx_cur_buf_head; +} + + +int uart_puts(const char *outstr) +{ + int was_empty = (tx_buf_head == tx_buf_tail); + + /* Put all characters in the output buffer */ + while (*outstr) { + if (__tx_char(*outstr++) != 0) + break; + } + + if (was_empty) + uart_tx_start(); + + /* Successful if we consumed all output */ + return *outstr ? EC_ERROR_OVERFLOW : EC_SUCCESS; +} + + +int uart_printf(const char *format, ...) +{ + static const char int_chars[] = "0123456789abcdef"; + static const char error_str[] = "ERROR"; + char intbuf[21]; /* Longest uint64 */ + int dropped_chars = 0; + int is_left; + int pad_zero; + int pad_width; + int was_empty = (tx_buf_head == tx_buf_tail); + va_list args; + char *vstr; + int vlen; + + va_start(args, format); + + while (*format && !dropped_chars) { + int c = *format++; + + /* Copy normal characters */ + if (c != '%') { + dropped_chars |= __tx_char(c); + continue; + } + + /* Get first format character */ + c = *format++; + + /* Send "%" for "%%" input */ + if (c == '%' || c == '\0') { + dropped_chars |= __tx_char('%'); + continue; + } + + /* Handle %c */ + if (c == 'c') { + c = va_arg(args, int); + dropped_chars |= __tx_char(c); + continue; + } + + /* Handle left-justification ("%-5s") */ + is_left = (c == '-'); + if (is_left) + c = *format++; + + /* Handle padding with 0's */ + pad_zero = (c == '0'); + if (pad_zero) + c = *format++; + + /* Count padding length */ + pad_width = 0; + while (c >= '0' && c <= '9') { + pad_width = (10 * pad_width) + c - '0'; + c = *format++; + } + if (pad_width > 80) { + /* Sanity check for width failed */ + format = error_str; + continue; + } + + if (c == 's') { + vstr = va_arg(args, char *); + if (vstr == NULL) + vstr = "(NULL)"; + } else { + uint32_t v; + int is_negative = 0; + int base = 10; + + /* TODO: (crosbug.com/p/7490) handle "%l" prefix for + * uint64_t */ + + v = va_arg(args, uint32_t); + + switch (c) { + case 'd': + if ((int)v < 0) { + is_negative = 1; + v = -v; + } + break; + case 'u': + break; + case 'x': + case 'p': + base = 16; + break; + default: + format = error_str; + } + if (format == error_str) + continue; /* Bad format specifier */ + + /* Convert integer to string, starting at end of + * buffer and working backwards. */ + vstr = intbuf + sizeof(intbuf) - 1; + *(vstr) = '\0'; + + if (!v) + *(--vstr) = '0'; + + while (v) { + *(--vstr) = int_chars[v % base]; + v /= base; + } + if (is_negative) + *(--vstr) = '-'; + } + + /* Copy string (or stringified integer) */ + vlen = strlen(vstr); + while (vlen < pad_width && !is_left) { + dropped_chars |= __tx_char(pad_zero ? '0' : ' '); + vlen++; + } + while (*vstr) + dropped_chars |= __tx_char(*vstr++); + while (vlen < pad_width && is_left) { + dropped_chars |= __tx_char(' '); + vlen++; + } + } + va_end(args); + + if (was_empty) + uart_tx_start(); + + /* Successful if we consumed all output */ + return dropped_chars ? EC_ERROR_OVERFLOW : EC_SUCCESS; +} + +void uart_flush_output(void) +{ + /* Wait for buffer to empty */ + while (tx_buf_head != tx_buf_tail) { + /* It's possible we're in some other interrupt, and the + * previous context was doing a printf() or puts() but hadn't + * enabled the UART interrupt. Check if the interrupt is + * disabled, and if so, re-enable and trigger it. Note that + * this check is inside the while loop, so we'll be safe even + * if the context switches away from us to another partial + * printf() and back. */ + if (uart_tx_stopped()) + uart_tx_start(); + } + + /* Wait for transmit FIFO empty */ + uart_tx_flush(); +} + +void uart_emergency_flush(void) +{ + do { + /* Copy output from buffer until TX fifo full + * or output buffer empty + */ + while (uart_tx_ready() && + (tx_buf_head != tx_buf_tail)) { + uart_write_char(tx_buf[tx_buf_tail]); + tx_buf_tail = TX_BUF_NEXT(tx_buf_tail); + } + /* Wait for transmit FIFO empty */ + uart_tx_flush(); + } while (tx_buf_head != tx_buf_tail); +} + + +void uart_flush_input(void) +{ + /* Disable interrupts */ + uart_disable_interrupt(); + + /* Empty the hardware FIFO */ + uart_process(); + + /* Clear the input buffer */ + rx_cur_buf_head = 0; + rx_buf_tail = rx_buf_head; + + /* Re-enable interrupts */ + uart_enable_interrupt(); +} + + +int uart_peek(int c) +{ + int index = -1; + int i = 0; + + /* Disable interrupts while we pull characters out, because the + * interrupt handler can also modify the tail pointer. */ + uart_disable_interrupt(); + + /* Call interrupt handler to empty the hardware FIFO. The minimum + * FIFO trigger depth is 1/8 (2 chars), so this is the only way to + * ensure we've pulled the very last character out of the FIFO. */ + uart_process(); + + for (i = 0; i < rx_cur_buf_head; ++i) { + if (rx_cur_buf[i] == c) { + index = i; + break; + } + } + + /* Re-enable interrupts */ + uart_enable_interrupt(); + + return index; +} + + +int uart_getc(void) +{ + int c; + + /* Disable interrupts */ + uart_disable_interrupt(); + + /* Call interrupt handler to empty the hardware FIFO */ + uart_process(); + + if (rx_buf_tail == rx_buf_head) { + c = -1; /* No pending input */ + } else { + c = rx_buf[rx_buf_tail]; + rx_buf_tail = RX_BUF_NEXT(rx_buf_tail); + } + + /* Re-enable interrupts */ + uart_enable_interrupt(); + + return c; +} + + +int uart_gets(char *dest, int size) +{ + int got = 0; + int c; + + /* Disable interrupts while we pull characters out, because the + * interrupt handler can also modify the tail pointer. */ + uart_disable_interrupt(); + + /* Call interrupt handler to empty the hardware FIFO */ + uart_process(); + + /* Remove the stashed command if any. */ + if (cmd_history_ptr != cmd_history_head) + cmd_history_head = CMD_HIST_PREV(cmd_history_head); + + /* Record last command. */ + if (!(rx_cur_buf_head == 1 && rx_cur_buf[0] == '\n')) + history_save(); + cmd_history_ptr = cmd_history_head; + + /* Read characters */ + while (got < size - 1 && got < rx_cur_buf_head) { + c = rx_cur_buf[got]; + dest[got++] = c; + if (c == '\n') + break; /* Stop on newline */ + } + rx_cur_buf_ptr = 0; + rx_cur_buf_head = 0; + rx_cur_buf_tail = rx_cur_buf_head; + + /* Re-enable interrupts */ + uart_enable_interrupt(); + + /* Null-terminate */ + dest[got] = '\0'; + + /* Return the length we got */ + return got; +} |