234 lines
4.6 KiB
C
234 lines
4.6 KiB
C
/* Copyright 2019 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.
|
|
*/
|
|
|
|
#include "common.h"
|
|
#include "config.h"
|
|
#include "console.h"
|
|
#include "crc.h"
|
|
#include "link_defs.h"
|
|
#include "printf.h"
|
|
#include "queue.h"
|
|
#include "task.h"
|
|
#include "timer.h"
|
|
#include "usb-stream.h"
|
|
|
|
#ifdef CONFIG_USB_CONSOLE
|
|
/*
|
|
* CONFIG_USB_CONSOLE and CONFIG_USB_CONSOLE_STREAM should be defined
|
|
* exclusively each other.
|
|
*/
|
|
#error "Do not enable CONFIG_USB_CONSOLE."
|
|
#endif
|
|
|
|
/* Console output macro */
|
|
#define USB_CONSOLE_TIMEOUT_US (30 * MSEC)
|
|
|
|
#define QUEUE_SIZE_USB_TX CONFIG_USB_CONSOLE_TX_BUF_SIZE
|
|
#define QUEUE_SIZE_USB_RX USB_MAX_PACKET_SIZE
|
|
|
|
static void usb_console_wr(struct queue_policy const *policy, size_t count);
|
|
static void uart_console_rd(struct queue_policy const *policy, size_t count);
|
|
|
|
|
|
static int last_tx_ok = 1;
|
|
|
|
/*
|
|
* Start enabled, so we can queue early debug output before the board gets
|
|
* around to calling usb_console_enable().
|
|
*/
|
|
static int is_enabled = 1;
|
|
|
|
/*
|
|
* But start read-only, so we don't accept console input until we explicitly
|
|
* decide that we're ready for it.
|
|
*/
|
|
static int is_readonly = 1;
|
|
|
|
/*
|
|
* This is a usb_console producer policy, which wakes up CONSOLE task whenever
|
|
* rx_q gets new data added. This shall be called by rx_stream_handler() in
|
|
* usb-stream.c.
|
|
*/
|
|
static struct queue_policy const usb_console_policy = {
|
|
.add = usb_console_wr,
|
|
.remove = uart_console_rd,
|
|
};
|
|
|
|
static struct queue const tx_q = QUEUE_NULL(QUEUE_SIZE_USB_TX, uint8_t);
|
|
static struct queue const rx_q = QUEUE(QUEUE_SIZE_USB_RX, uint8_t,
|
|
usb_console_policy);
|
|
|
|
struct usb_stream_config const usb_console;
|
|
|
|
USB_STREAM_CONFIG(usb_console,
|
|
USB_IFACE_CONSOLE,
|
|
USB_STR_CONSOLE_NAME,
|
|
USB_EP_CONSOLE,
|
|
USB_MAX_PACKET_SIZE,
|
|
USB_MAX_PACKET_SIZE,
|
|
rx_q,
|
|
tx_q)
|
|
|
|
static void usb_console_wr(struct queue_policy const *policy, size_t count)
|
|
{
|
|
console_has_input();
|
|
}
|
|
|
|
static void uart_console_rd(struct queue_policy const *policy, size_t count)
|
|
{
|
|
/* do nothing */
|
|
}
|
|
|
|
static void handle_output(void)
|
|
{
|
|
/* Wake up the Tx FIFO handler */
|
|
usb_console.consumer.ops->written(&usb_console.consumer, 1);
|
|
}
|
|
|
|
static int usb_wait_console(void)
|
|
{
|
|
timestamp_t deadline = get_time();
|
|
int wait_time_us = 1;
|
|
|
|
if (!is_enabled || !tx_fifo_is_ready(&usb_console))
|
|
return EC_SUCCESS;
|
|
|
|
deadline.val += USB_CONSOLE_TIMEOUT_US;
|
|
|
|
/*
|
|
* If the USB console is not used, Tx buffer would never free up.
|
|
* In this case, let's drop characters immediately instead of sitting
|
|
* for some time just to time out. On the other hand, if the last
|
|
* Tx is good, it's likely the host is there to receive data, and
|
|
* we should wait so that we don't clobber the buffer.
|
|
*/
|
|
if (last_tx_ok) {
|
|
while (queue_space(&tx_q) < USB_MAX_PACKET_SIZE ||
|
|
!*usb_console.is_reset) {
|
|
if (timestamp_expired(deadline, NULL) ||
|
|
in_interrupt_context()) {
|
|
last_tx_ok = 0;
|
|
return EC_ERROR_TIMEOUT;
|
|
}
|
|
if (wait_time_us < MSEC)
|
|
udelay(wait_time_us);
|
|
else
|
|
usleep(wait_time_us);
|
|
wait_time_us *= 2;
|
|
}
|
|
} else {
|
|
last_tx_ok = queue_space(&tx_q);
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
#ifdef CONFIG_USB_CONSOLE_CRC
|
|
static uint32_t usb_tx_crc_ctx;
|
|
|
|
void usb_console_crc_init(void)
|
|
{
|
|
crc32_ctx_init(&usb_tx_crc_ctx);
|
|
}
|
|
|
|
uint32_t usb_console_crc(void)
|
|
{
|
|
return crc32_ctx_result(&usb_tx_crc_ctx);
|
|
}
|
|
#endif
|
|
|
|
static int __tx_char(void *context, int c)
|
|
{
|
|
int ret;
|
|
|
|
if (c == '\n') {
|
|
ret = __tx_char(NULL, '\r');
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_USB_CONSOLE_CRC
|
|
crc32_ctx_hash8(&usb_tx_crc_ctx, c);
|
|
|
|
while (queue_add_unit(&tx_q, &c) != 1)
|
|
usleep(500);
|
|
|
|
return EC_SUCCESS;
|
|
#else
|
|
/* Return 0 on success */
|
|
return queue_add_unit(&tx_q, &c) ? EC_SUCCESS : EC_ERROR_OVERFLOW;
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* Public USB console implementation below.
|
|
*/
|
|
int usb_getc(void)
|
|
{
|
|
int c;
|
|
|
|
if (is_readonly || !is_enabled)
|
|
return -1;
|
|
|
|
if (!queue_remove_unit(&rx_q, &c))
|
|
return -1;
|
|
|
|
return c;
|
|
}
|
|
|
|
int usb_puts(const char *outstr)
|
|
{
|
|
int ret;
|
|
|
|
if (!is_enabled)
|
|
return EC_SUCCESS;
|
|
|
|
ret = usb_wait_console();
|
|
if (ret)
|
|
return ret;
|
|
|
|
while (*outstr) {
|
|
ret = __tx_char(NULL, *outstr++);
|
|
if (ret)
|
|
break;
|
|
}
|
|
handle_output();
|
|
|
|
return ret;
|
|
}
|
|
|
|
int usb_putc(int c)
|
|
{
|
|
static char string[2] = { 0, '\0' };
|
|
|
|
string[0] = c;
|
|
|
|
return usb_puts(string);
|
|
}
|
|
|
|
int usb_vprintf(const char *format, va_list args)
|
|
{
|
|
int ret;
|
|
|
|
if (!is_enabled)
|
|
return EC_SUCCESS;
|
|
|
|
ret = usb_wait_console();
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = vfnprintf(__tx_char, NULL, format, args);
|
|
|
|
handle_output();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void usb_console_enable(int enabled, int readonly)
|
|
{
|
|
is_enabled = enabled;
|
|
is_readonly = readonly;
|
|
}
|