504 lines
12 KiB
C
504 lines
12 KiB
C
|
/* 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.
|
||
|
*/
|
||
|
|
||
|
/* Common code to do UART buffering and printing */
|
||
|
|
||
|
#include <stdarg.h>
|
||
|
|
||
|
#include "common.h"
|
||
|
#include "console.h"
|
||
|
#include "hooks.h"
|
||
|
#include "host_command.h"
|
||
|
#include "link_defs.h"
|
||
|
#include "printf.h"
|
||
|
#include "system.h"
|
||
|
#include "task.h"
|
||
|
#include "timer.h"
|
||
|
#include "uart.h"
|
||
|
#include "util.h"
|
||
|
|
||
|
/* 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))
|
||
|
|
||
|
/* 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))
|
||
|
|
||
|
/*
|
||
|
* Interval between rechecking the receive DMA head pointer, after a character
|
||
|
* of input has been detected by the normal tick task. There will be
|
||
|
* CONFIG_UART_RX_DMA_RECHECKS rechecks between this tick and the next tick.
|
||
|
*/
|
||
|
#define RX_DMA_RECHECK_INTERVAL (HOOK_TICK_INTERVAL / \
|
||
|
(CONFIG_UART_RX_DMA_RECHECKS + 1))
|
||
|
|
||
|
/* Transmit and receive buffers */
|
||
|
static volatile char tx_buf[CONFIG_UART_TX_BUF_SIZE]
|
||
|
__uncached __preserved_logs(tx_buf);
|
||
|
static volatile int tx_buf_head __preserved_logs(tx_buf_head);
|
||
|
static volatile int tx_buf_tail __preserved_logs(tx_buf_tail);
|
||
|
static volatile char rx_buf[CONFIG_UART_RX_BUF_SIZE] __uncached;
|
||
|
static volatile int rx_buf_head;
|
||
|
static volatile int rx_buf_tail;
|
||
|
static int tx_snapshot_head;
|
||
|
static int tx_snapshot_tail;
|
||
|
static int tx_last_snapshot_head;
|
||
|
static int tx_next_snapshot_head;
|
||
|
static int tx_checksum __preserved_logs(tx_checksum);
|
||
|
|
||
|
static int uart_buffer_calc_checksum(void)
|
||
|
{
|
||
|
return tx_buf_head ^ tx_buf_tail;
|
||
|
}
|
||
|
|
||
|
|
||
|
void uart_init_buffer(void)
|
||
|
{
|
||
|
if (tx_checksum != uart_buffer_calc_checksum() ||
|
||
|
!IN_RANGE(tx_buf_head, 0, CONFIG_UART_TX_BUF_SIZE) ||
|
||
|
!IN_RANGE(tx_buf_tail, 0, CONFIG_UART_TX_BUF_SIZE)) {
|
||
|
tx_buf_head = 0;
|
||
|
tx_buf_tail = 0;
|
||
|
tx_checksum = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Put a single character into the transmit buffer.
|
||
|
*
|
||
|
* Does not enable the transmit interrupt; assumes that happens elsewhere.
|
||
|
*
|
||
|
* @param context Context; ignored.
|
||
|
* @param c Character to write.
|
||
|
* @return 0 if the character was transmitted, 1 if it was dropped.
|
||
|
*/
|
||
|
static int __tx_char(void *context, int c)
|
||
|
{
|
||
|
int tx_buf_next, tx_buf_new_tail;
|
||
|
|
||
|
/* Do newline to CRLF translation */
|
||
|
if (c == '\n' && __tx_char(NULL, '\r'))
|
||
|
return 1;
|
||
|
|
||
|
#if defined CONFIG_POLLING_UART
|
||
|
(void) tx_buf_next;
|
||
|
(void) tx_buf_new_tail;
|
||
|
uart_write_char(c);
|
||
|
#else
|
||
|
|
||
|
tx_buf_next = TX_BUF_NEXT(tx_buf_head);
|
||
|
if (tx_buf_next == tx_buf_tail)
|
||
|
return 1;
|
||
|
|
||
|
/*
|
||
|
* If we do a READ_RECENT, the buffer may have wrapped around, and
|
||
|
* we'll drop most of the logs in this case. Make sure the place
|
||
|
* we read from in that case is always ahead of the new tx_buf_head.
|
||
|
*
|
||
|
* We also want to make sure that the next time we snapshot and want
|
||
|
* to READ_RECENT, we don't start reading from a stale tail.
|
||
|
*/
|
||
|
tx_buf_new_tail = TX_BUF_NEXT(tx_buf_next);
|
||
|
if (tx_buf_next == tx_last_snapshot_head &&
|
||
|
tx_last_snapshot_head != tx_snapshot_head)
|
||
|
tx_last_snapshot_head = tx_buf_new_tail;
|
||
|
if (tx_buf_next == tx_next_snapshot_head)
|
||
|
tx_next_snapshot_head = tx_buf_new_tail;
|
||
|
|
||
|
tx_buf[tx_buf_head] = c;
|
||
|
tx_buf_head = tx_buf_next;
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_PRESERVE_LOGS))
|
||
|
tx_checksum = uart_buffer_calc_checksum();
|
||
|
#endif
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_UART_TX_DMA
|
||
|
|
||
|
/**
|
||
|
* Process UART output via DMA
|
||
|
*/
|
||
|
void uart_process_output(void)
|
||
|
{
|
||
|
/* Size of current DMA transfer */
|
||
|
static int tx_dma_in_progress;
|
||
|
|
||
|
/*
|
||
|
* Get head pointer now, to avoid math problems if some other task
|
||
|
* or interrupt adds output during this call.
|
||
|
*/
|
||
|
int head = tx_buf_head;
|
||
|
|
||
|
/* If DMA is still busy, nothing to do. */
|
||
|
if (!uart_tx_dma_ready())
|
||
|
return;
|
||
|
|
||
|
/* If a previous DMA transfer completed, free up the buffer it used */
|
||
|
if (tx_dma_in_progress) {
|
||
|
tx_buf_tail = (tx_buf_tail + tx_dma_in_progress) &
|
||
|
(CONFIG_UART_TX_BUF_SIZE - 1);
|
||
|
tx_dma_in_progress = 0;
|
||
|
|
||
|
if (IS_ENABLED(CONFIG_PRESERVE_LOGS))
|
||
|
tx_checksum = uart_buffer_calc_checksum();
|
||
|
}
|
||
|
|
||
|
/* Disable DMA-done interrupt if nothing to send */
|
||
|
if (head == tx_buf_tail) {
|
||
|
uart_tx_stop();
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Get the largest contiguous block of output. If the transmit buffer
|
||
|
* wraps, only use the part before the wrap.
|
||
|
*/
|
||
|
tx_dma_in_progress = (head > tx_buf_tail ? head :
|
||
|
CONFIG_UART_TX_BUF_SIZE) - tx_buf_tail;
|
||
|
|
||
|
uart_tx_dma_start((char *)(tx_buf + tx_buf_tail), tx_dma_in_progress);
|
||
|
}
|
||
|
|
||
|
#else /* !CONFIG_UART_TX_DMA */
|
||
|
|
||
|
void uart_process_output(void)
|
||
|
{
|
||
|
/* 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 (IS_ENABLED(CONFIG_PRESERVE_LOGS))
|
||
|
tx_checksum = uart_buffer_calc_checksum();
|
||
|
}
|
||
|
|
||
|
/* If output buffer is empty, disable transmit interrupt */
|
||
|
if (tx_buf_tail == tx_buf_head)
|
||
|
uart_tx_stop();
|
||
|
}
|
||
|
|
||
|
#endif /* !CONFIG_UART_TX_DMA */
|
||
|
|
||
|
#ifdef CONFIG_UART_RX_DMA
|
||
|
#ifdef CONFIG_UART_INPUT_FILTER /* TODO(crosbug.com/p/36745): */
|
||
|
#error "Filtering the UART input with DMA enabled is NOT SUPPORTED!"
|
||
|
#endif
|
||
|
|
||
|
void uart_process_input(void);
|
||
|
DECLARE_DEFERRED(uart_process_input);
|
||
|
|
||
|
void uart_process_input(void)
|
||
|
{
|
||
|
static int fast_rechecks;
|
||
|
int cur_head = rx_buf_head;
|
||
|
|
||
|
/* Update receive buffer head from current DMA receive pointer */
|
||
|
rx_buf_head = uart_rx_dma_head();
|
||
|
|
||
|
if (rx_buf_head != cur_head) {
|
||
|
console_has_input();
|
||
|
fast_rechecks = CONFIG_UART_RX_DMA_RECHECKS;
|
||
|
}
|
||
|
|
||
|
/*
|
||
|
* Input is checked once a tick when the console is idle. When input
|
||
|
* is received, check more frequently for a bit, so that the console is
|
||
|
* more responsive.
|
||
|
*/
|
||
|
if (fast_rechecks) {
|
||
|
fast_rechecks--;
|
||
|
hook_call_deferred(&uart_process_input_data,
|
||
|
RX_DMA_RECHECK_INTERVAL);
|
||
|
}
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_TICK, uart_process_input, HOOK_PRIO_DEFAULT);
|
||
|
|
||
|
#else /* !CONFIG_UART_RX_DMA */
|
||
|
|
||
|
void uart_process_input(void)
|
||
|
{
|
||
|
int got_input = 0;
|
||
|
|
||
|
/* Copy input from buffer until RX fifo empty */
|
||
|
while (uart_rx_available()) {
|
||
|
int c = uart_read_char();
|
||
|
int rx_buf_next = RX_BUF_NEXT(rx_buf_head);
|
||
|
|
||
|
#ifdef CONFIG_UART_INPUT_FILTER
|
||
|
/* Intercept the input before it goes to the console */
|
||
|
if (uart_input_filter(c))
|
||
|
continue;
|
||
|
#endif
|
||
|
|
||
|
if (rx_buf_next != rx_buf_tail) {
|
||
|
/* Buffer all other input */
|
||
|
rx_buf[rx_buf_head] = c;
|
||
|
rx_buf_head = rx_buf_next;
|
||
|
got_input = 1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (got_input)
|
||
|
console_has_input();
|
||
|
}
|
||
|
|
||
|
void uart_clear_input(void)
|
||
|
{
|
||
|
int scratch __attribute__ ((unused));
|
||
|
while (uart_rx_available())
|
||
|
scratch = uart_read_char();
|
||
|
rx_buf_head = rx_buf_tail = 0;
|
||
|
}
|
||
|
|
||
|
#endif /* !CONFIG_UART_RX_DMA */
|
||
|
|
||
|
int uart_putc(int c)
|
||
|
{
|
||
|
int rv = __tx_char(NULL, c);
|
||
|
|
||
|
uart_tx_start();
|
||
|
|
||
|
return rv ? EC_ERROR_OVERFLOW : EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
int uart_puts(const char *outstr)
|
||
|
{
|
||
|
/* Put all characters in the output buffer */
|
||
|
while (*outstr) {
|
||
|
if (__tx_char(NULL, *outstr++) != 0)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
uart_tx_start();
|
||
|
|
||
|
/* Successful if we consumed all output */
|
||
|
return *outstr ? EC_ERROR_OVERFLOW : EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
int uart_put(const char *out, int len)
|
||
|
{
|
||
|
/* Put all characters in the output buffer */
|
||
|
while (len--) {
|
||
|
if (__tx_char(NULL, *out++) != 0)
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
uart_tx_start();
|
||
|
|
||
|
/* Successful if we consumed all output */
|
||
|
return len ? EC_ERROR_OVERFLOW : EC_SUCCESS;
|
||
|
}
|
||
|
|
||
|
int uart_vprintf(const char *format, va_list args)
|
||
|
{
|
||
|
int rv = vfnprintf(__tx_char, NULL, format, args);
|
||
|
|
||
|
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;
|
||
|
}
|
||
|
|
||
|
void uart_flush_output(void)
|
||
|
{
|
||
|
/* If UART not initialized ignore flush request. */
|
||
|
if (!uart_init_done())
|
||
|
return;
|
||
|
|
||
|
/* Loop until buffer is empty */
|
||
|
while (tx_buf_head != tx_buf_tail) {
|
||
|
if (in_interrupt_context()) {
|
||
|
/*
|
||
|
* Explicitly process UART output, since the UART
|
||
|
* interrupt may not be able to preempt the interrupt
|
||
|
* we're in now.
|
||
|
*/
|
||
|
uart_process_output();
|
||
|
} else {
|
||
|
/*
|
||
|
* It's possible we switched from a previous context
|
||
|
* which 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.
|
||
|
*/
|
||
|
uart_tx_start();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Wait for transmit FIFO empty */
|
||
|
uart_tx_flush();
|
||
|
}
|
||
|
|
||
|
int uart_getc(void)
|
||
|
{
|
||
|
/* Look for a non-flow-control character */
|
||
|
while (rx_buf_tail != rx_buf_head) {
|
||
|
int c = rx_buf[rx_buf_tail];
|
||
|
rx_buf_tail = RX_BUF_NEXT(rx_buf_tail);
|
||
|
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
/* If we're still here, no input */
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
int uart_buffer_empty(void)
|
||
|
{
|
||
|
return tx_buf_head == tx_buf_tail;
|
||
|
}
|
||
|
|
||
|
int uart_buffer_full(void)
|
||
|
{
|
||
|
return TX_BUF_NEXT(tx_buf_head) == tx_buf_tail;
|
||
|
}
|
||
|
|
||
|
#ifdef CONFIG_UART_RX_DMA
|
||
|
static void uart_rx_dma_init(void)
|
||
|
{
|
||
|
/* Start receiving */
|
||
|
uart_rx_dma_start((char *)rx_buf, CONFIG_UART_RX_BUF_SIZE);
|
||
|
}
|
||
|
DECLARE_HOOK(HOOK_INIT, uart_rx_dma_init, HOOK_PRIO_DEFAULT);
|
||
|
#endif
|
||
|
|
||
|
/*****************************************************************************/
|
||
|
/* Host commands */
|
||
|
|
||
|
static enum ec_status
|
||
|
host_command_console_snapshot(struct host_cmd_handler_args *args)
|
||
|
{
|
||
|
return uart_console_read_buffer_init();
|
||
|
}
|
||
|
DECLARE_HOST_COMMAND(EC_CMD_CONSOLE_SNAPSHOT,
|
||
|
host_command_console_snapshot,
|
||
|
EC_VER_MASK(0));
|
||
|
|
||
|
static enum ec_status
|
||
|
host_command_console_read(struct host_cmd_handler_args *args)
|
||
|
{
|
||
|
if (args->version == 0) {
|
||
|
/*
|
||
|
* Prior versions of this command only support reading from
|
||
|
* an entire snapshot, not just the output since the last
|
||
|
* snapshot.
|
||
|
*/
|
||
|
return uart_console_read_buffer(
|
||
|
CONSOLE_READ_NEXT,
|
||
|
(char *)args->response,
|
||
|
args->response_max,
|
||
|
&args->response_size);
|
||
|
#ifdef CONFIG_CONSOLE_ENABLE_READ_V1
|
||
|
} else if (args->version == 1) {
|
||
|
const struct ec_params_console_read_v1 *p;
|
||
|
|
||
|
/* Check the params to figure out where to start reading. */
|
||
|
p = args->params;
|
||
|
return uart_console_read_buffer(
|
||
|
p->subcmd,
|
||
|
(char *)args->response,
|
||
|
args->response_max,
|
||
|
&args->response_size);
|
||
|
#endif
|
||
|
}
|
||
|
return EC_RES_INVALID_PARAM;
|
||
|
}
|
||
|
DECLARE_HOST_COMMAND(EC_CMD_CONSOLE_READ,
|
||
|
host_command_console_read,
|
||
|
EC_VER_MASK(0)
|
||
|
#ifdef CONFIG_CONSOLE_ENABLE_READ_V1
|
||
|
| EC_VER_MASK(1)
|
||
|
#endif
|
||
|
);
|
||
|
|
||
|
enum ec_status uart_console_read_buffer_init(void)
|
||
|
{
|
||
|
/* Assume the whole circular buffer is full */
|
||
|
tx_snapshot_head = tx_buf_head;
|
||
|
tx_snapshot_tail = TX_BUF_NEXT(tx_snapshot_head);
|
||
|
/* Set up pointer for just the new part of the buffer */
|
||
|
tx_last_snapshot_head = tx_next_snapshot_head;
|
||
|
tx_next_snapshot_head = tx_buf_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;
|
||
|
}
|
||
|
|
||
|
int uart_console_read_buffer(uint8_t type,
|
||
|
char *dest,
|
||
|
uint16_t dest_size,
|
||
|
uint16_t *write_count)
|
||
|
{
|
||
|
int *tail;
|
||
|
|
||
|
switch (type) {
|
||
|
case CONSOLE_READ_NEXT:
|
||
|
tail = &tx_snapshot_tail;
|
||
|
break;
|
||
|
case CONSOLE_READ_RECENT:
|
||
|
tail = &tx_last_snapshot_head;
|
||
|
break;
|
||
|
default:
|
||
|
return EC_RES_INVALID_PARAM;
|
||
|
}
|
||
|
|
||
|
/* If no snapshot data, return empty response */
|
||
|
if (tx_snapshot_head == *tail)
|
||
|
return EC_RES_SUCCESS;
|
||
|
|
||
|
/* Copy data to response */
|
||
|
while (*tail != tx_snapshot_head && *write_count < dest_size - 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[*tail]) {
|
||
|
*(dest++) = tx_buf[*tail];
|
||
|
(*write_count)++;
|
||
|
}
|
||
|
|
||
|
*tail = TX_BUF_NEXT(*tail);
|
||
|
}
|
||
|
|
||
|
/* Null-terminate */
|
||
|
*(dest++) = '\0';
|
||
|
(*write_count)++;
|
||
|
|
||
|
return EC_RES_SUCCESS;
|
||
|
}
|
||
|
|