blob: 1dd56aed60087552ded57b81049fe78c8086bc4c [file] [log] [blame]
/* 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 "common.h"
#include "console.h"
#include "host_command.h"
#include "printf.h"
#include "system.h"
#include "task.h"
#include "uart.h"
#include "util.h"
/*
* Transmit and receive buffer sizes must be power of 2 for the macros below
* to work properly.
*/
#ifndef CONFIG_UART_TX_BUF_SIZE
#define CONFIG_UART_TX_BUF_SIZE 512
#endif
#ifndef CONFIG_UART_RX_BUF_SIZE
/* Must be larger than RX_LINE_SIZE to copy and paste scripts */
#define CONFIG_UART_RX_BUF_SIZE 128
#endif
#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) & (CONFIG_UART_TX_BUF_SIZE - 1))
#define RX_BUF_NEXT(i) (((i) + 1) & (CONFIG_UART_RX_BUF_SIZE - 1))
#define RX_BUF_PREV(i) (((i) - 1) & (CONFIG_UART_RX_BUF_SIZE - 1))
#define CMD_HIST_NEXT(i) (((i) + 1) & (HISTORY_SIZE - 1))
#define CMD_HIST_PREV(i) (((i) - 1) & (HISTORY_SIZE - 1))
/* Macros to calculate difference of pointers in the circular buffers. */
#define TX_BUF_DIFF(i, j) (((i) - (j)) & (CONFIG_UART_TX_BUF_SIZE - 1))
#define RX_BUF_DIFF(i, j) (((i) - (j)) & (CONFIG_UART_RX_BUF_SIZE - 1))
/* ASCII control character; for example, CTRL('C') = ^C */
#define CTRL(c) ((c) - '@')
/* Transmit and receive buffers */
static volatile char tx_buf[CONFIG_UART_TX_BUF_SIZE];
static volatile int tx_buf_head;
static volatile int tx_buf_tail;
static volatile char rx_buf[CONFIG_UART_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 tx_snapshot_head;
static int tx_snapshot_tail;
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;
/* 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;
/* 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. We only
* have a single transmit buffer, so context is ignored. */
static int __tx_char(void *context, int c)
{
int tx_buf_next;
/* Do newline to CRLF translation */
if (console_mode && c == '\n' && __tx_char(NULL, '\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;
}
/**
* Write a number directly to the UART.
*
* @param val number to write; must be >1.
*/
static void uart_write_int(int val)
{
if (val <= 0)
return;
if (val > 9)
uart_write_int(val / 10);
uart_write_char((val % 10) + '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_end(void)
{
if (rx_cur_buf_ptr == rx_cur_buf_head)
return;
uart_write_char(0x1B);
uart_write_char('[');
uart_write_int(rx_cur_buf_head - rx_cur_buf_ptr);
uart_write_char('C');
rx_cur_buf_ptr = rx_cur_buf_head;
}
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 move_rx_ptr_begin(void)
{
if (rx_cur_buf_ptr == 0)
return;
uart_write_char(0x1B);
uart_write_char('[');
uart_write_int(rx_cur_buf_ptr);
uart_write_char('D');
rx_cur_buf_ptr = 0;
}
static void repeat_char(char c, int cnt)
{
while (cnt--)
uart_write_char(c);
}
static void handle_backspace(void)
{
int ptr;
if (!rx_cur_buf_ptr)
return; /* Already at beginning of line */
/* Move cursor back */
uart_write_char('\b');
/* Move texts after cursor and also update rx buffer. */
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 to correct position */
uart_write_char(' ');
repeat_char('\b', ptr - rx_cur_buf_ptr + 1);
--rx_cur_buf_head;
--rx_cur_buf_ptr;
}
static void handle_kill(void)
{
if (rx_cur_buf_ptr == rx_cur_buf_head)
return;
/* Space over all following characters */
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 reprint_current(void)
{
int ptr;
uart_write_char(CTRL('L'));
uart_write_char('>');
uart_write_char(' ');
for (ptr = 0; ptr < rx_cur_buf_head; ptr++)
uart_write_char(rx_cur_buf[ptr]);
repeat_char('\b', ptr - rx_cur_buf_ptr);
}
static void insert_char(char c)
{
int ptr;
/* On overflow, discard input */
if (rx_cur_buf_head == RX_LINE_SIZE && c != '\n')
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 CONFIG_UART_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 = RX_BUF_NEXT(cmd_history[CMD_HIST_PREV(hist_id)].head);
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 > CONFIG_UART_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;
}
/**
* Escape code handler
*
* @param c Next received character.
*/
static void handle_esc(int c)
{
switch (esc_state) {
case ESC_START:
if (c == '[') {
esc_state = ESC_BRACKET;
return;
} else if (c == 'O') {
esc_state = ESC_O;
return;
}
break;
case ESC_BRACKET:
if (c == '1') {
esc_state = ESC_BRACKET_1;
return;
} else if (c == '3') {
esc_state = ESC_BRACKET_3;
return;
}
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();
break;
case ESC_O:
if (c == 'F') /* End key */
move_rx_ptr_end();
break;
case ESC_BRACKET_1:
if (c == '~') /* Home key */
move_rx_ptr_begin();
break;
case ESC_BRACKET_3:
if (c == '~') { /* Del key */
if (rx_cur_buf_ptr != rx_cur_buf_head) {
move_rx_ptr_fwd();
handle_backspace();
}
}
break;
default:
break;
}
/* Check if the escape code is done */
if (isalpha(c) || c == '~')
esc_state = ESC_OUTSIDE;
else
esc_state = ESC_BAD;
}
/**
* Handle next character of console input.
*/
static void handle_console_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) {
handle_esc(c);
return;
}
/* Handle control characters */
if (c == '\b' || c == 0x7f) {
handle_backspace();
} else if (c == CTRL('A')) {
move_rx_ptr_begin();
} else if (c == CTRL('E')) {
move_rx_ptr_end();
} else if (c == CTRL('L')) {
reprint_current();
} else if (c == CTRL('K')) {
handle_kill();
} else if (c == CTRL('N')) {
history_next();
} else if (c == CTRL('P')) {
history_prev();
} else if (c == '\n') { /* Newline */
uart_write_char('\r');
uart_write_char('\n');
insert_char(c);
console_has_input();
} else if (isprint(c)) {
/*
* Normal printable character. Echo directly to the transmit
* FIFO so we don't interfere with the transmit buffer.
*/
uart_write_char(c);
insert_char(c);
}
}
/* 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();
if (console_mode) {
/* Handle console mode echoing and translation */
handle_console_char(c);
} else {
/* Not in console mode, so simply store character */
insert_char(c);
}
}
/* 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)
{
/* Put all characters in the output buffer */
while (*outstr) {
if (__tx_char(NULL, *outstr++) != 0)
break;
}
if (uart_tx_stopped())
uart_tx_start();
/* Successful if we consumed all output */
return *outstr ? EC_ERROR_OVERFLOW : EC_SUCCESS;
}
int uart_vprintf(const char *format, va_list args)
{
int rv = vfnprintf(__tx_char, NULL, format, args);
if (uart_tx_stopped())
uart_tx_start();
return rv;
}
int uart_printf(const char *format, ...)
{
int rv;
va_list args;
va_start(args, format);
rv = uart_vprintf(format, args);
va_end(args);
return rv;
}
/* Add a character directly to the UART buffer */
static int emergency_txchar(void *format, int c)
{
/* Wait for space */
while (!uart_tx_ready())
;
/* Write the character */
uart_write_char(c);
return 0;
}
int uart_emergency_printf(const char *format, ...)
{
int rv;
va_list args;
va_start(args, format);
rv = vfnprintf(emergency_txchar, NULL, format, args);
va_end(args);
/* Wait for transmit FIFO empty */
uart_tx_flush();
return rv;
}
/* For use when debugging verified boot. We could wrap it with a real function,
* but it's rarely needed and this doesn't add any extra code. We have to
* declare it here in order for this trick to work. */
void VbExDebug(const char *format, ...)
__attribute__((weak, alias("uart_printf")));
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;
}
/*****************************************************************************/
/* Host commands */
static int host_command_console_snapshot(struct host_cmd_handler_args *args)
{
/*
* Only allowed on unlocked system, since console output contains
* keystroke data.
*/
if (system_is_locked())
return EC_ERROR_ACCESS_DENIED;
/* Assume the whole circular buffer is full */
tx_snapshot_head = tx_buf_head;
tx_snapshot_tail = TX_BUF_NEXT(tx_snapshot_head);
/*
* Immediately skip any unused bytes. This doesn't always work,
* because a higher-priority task or interrupt handler can write to the
* buffer while we're scanning it. This is acceptable because this
* command is only for debugging, and the failure mode is a bit of
* garbage at the beginning of the saved output. The saved buffer
* could also be overwritten by the head coming completely back around
* before we finish. The alternative would be to make a full copy of
* the transmit buffer, but that requires a lot of RAM.
*/
while (tx_snapshot_tail != tx_snapshot_head) {
if (tx_buf[tx_snapshot_tail])
break;
tx_snapshot_tail = TX_BUF_NEXT(tx_snapshot_tail);
}
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_CONSOLE_SNAPSHOT,
host_command_console_snapshot,
EC_VER_MASK(0));
static int host_command_console_read(struct host_cmd_handler_args *args)
{
char *dest = (char *)args->response;
/*
* Only allowed on unlocked system, since console output contains
* keystroke data.
*/
if (system_is_locked())
return EC_ERROR_ACCESS_DENIED;
/* If no snapshot data, return empty response */
if (tx_snapshot_head == tx_snapshot_tail)
return EC_RES_SUCCESS;
/* Copy data to response */
while (tx_snapshot_tail != tx_snapshot_head &&
args->response_size < args->response_max - 1) {
/*
* Copy only non-zero bytes, so that we don't copy unused
* bytes if the buffer hasn't completely rolled at boot.
*/
if (tx_buf[tx_snapshot_tail]) {
*(dest++) = tx_buf[tx_snapshot_tail];
args->response_size++;
}
tx_snapshot_tail = TX_BUF_NEXT(tx_snapshot_tail);
}
/* Null-terminate */
*(dest++) = '\0';
args->response_size++;
return EC_RES_SUCCESS;
}
DECLARE_HOST_COMMAND(EC_CMD_CONSOLE_READ,
host_command_console_read,
EC_VER_MASK(0));