Files
OpenCellular/common/uart_buffering.c
Randall Spangler cdd5c206cd stm32: Use DMA for UART receive
STM32 has a single-byte mailbox for UART I/O.  When the core clock
runs at 16Mhz we can service interrupts fast enough to handle 115200
baud input, but when we drop to 1MHz we drop characters.  Using DMA to
receive input solves this problem.

The STM32 DMA engine can only generate interrupts when the transfer is
half-done / all-done, so we need to poll the DMA receive-head-pointer
to see if individual characters have been received.  Do this in the
tick task (every 250ms).  When a character is received, poll more
quickly for a bit (5 times before the next tick) so the input console
is more responsive to typing.

BUG=chrome-os-partner:20485
BRANCH=none
TEST=Console is responsive to debug commands.  For example, help -> prints help
     apshutdown -> shuts down AP
     arrow keys -> move cursor and scroll through command history
     Ctrl+Q, help, wait a second, Ctrl+S -> help output printed after Ctrl+S

     Then in chip/stm32/config_chip.h, comment out #define CONFIG_UART_RX_DMA
     and rebuild/reflash the EC.  When the AP is up, the console works normally
     but after 'apshutdown', the EC drops to 1MHz core clock, and the arrow
     keys don't work.  (This step confirms that adding DMA support did not
     change the behavior of systems where CONFIG_UART_RX_DMA is not defined.)

Change-Id: I199448354824bd747c7b290ea7fd5ccf354c11bb
Signed-off-by: Randall Spangler <rspangler@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/169406
Reviewed-by: Simon Glass <sjg@chromium.org>
2013-09-16 23:31:07 +00:00

403 lines
9.8 KiB
C

/* 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 "hooks.h"
#include "host_command.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))
/* ASCII control character; for example, CTRL('C') = ^C */
#define CTRL(c) ((c) - '@')
/*
* 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];
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 int tx_snapshot_head;
static int tx_snapshot_tail;
static int uart_suspended;
/**
* 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;
/* Do newline to CRLF translation */
if (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;
}
#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 (uart_suspended)
return;
/* 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;
}
/* 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)
{
if (uart_suspended)
return;
/* 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();
}
#endif /* !CONFIG_UART_TX_DMA */
#ifdef CONFIG_UART_RX_DMA
void uart_process_input(void)
{
static int fast_rechecks;
int cur_head = rx_buf_head;
int i;
/* Update receive buffer head from current DMA receive pointer */
rx_buf_head = uart_rx_dma_head();
/* Handle software flow control characters */
for (i = cur_head; i != rx_buf_head; i = RX_BUF_NEXT(i)) {
int c = rx_buf[i];
if (c == CTRL('Q')) {
/* Software flow control - XOFF */
uart_suspended = 1;
uart_tx_stop();
} else if (c == CTRL('S')) {
/* Software flow control - XON */
uart_suspended = 0;
uart_tx_start();
}
}
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, RX_DMA_RECHECK_INTERVAL);
}
}
DECLARE_HOOK(HOOK_TICK, uart_process_input, HOOK_PRIO_DEFAULT);
DECLARE_DEFERRED(uart_process_input);
#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);
if (c == CTRL('Q')) {
/* Software flow control - XOFF */
uart_suspended = 1;
uart_tx_stop();
} else if (c == CTRL('S')) {
/* Software flow control - XON */
uart_suspended = 0;
uart_tx_start();
} else 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();
}
#endif /* !CONFIG_UART_RX_DMA */
int uart_putc(int c)
{
int rv = __tx_char(NULL, c);
if (!uart_suspended)
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;
}
if (!uart_suspended)
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_suspended)
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 is suspended, ignore flush request. */
if (uart_suspended)
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);
if (c != CTRL('Q') && c != CTRL('S'))
return c;
}
/* If we're still here, no input */
return -1;
}
#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 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));