From a5e74e6b45e2484fba2d7dc81ac054497d2576f3 Mon Sep 17 00:00:00 2001 From: Mary Ruthven Date: Mon, 14 Mar 2016 09:44:51 -0700 Subject: cr50: forward console through USB This change adds support for forwarding the EC console through USB. BUG=chrome-os-partner:49960 BRANCH=none TEST=load the google-serial udev rules in extra/usb_serial/, build the raiden.ko module, and then check that the console can be accessed from /dev/google/Cr50/serial/Shell Change-Id: I35e0bb39fdc8f9485a14c03eb3a4d2f024884e17 Signed-off-by: Mary Ruthven Reviewed-on: https://chromium-review.googlesource.com/334132 Tested-by: Bill Richardson Reviewed-by: Bill Richardson --- board/cr50/board.h | 6 +- chip/g/usb_console.c | 275 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 188 insertions(+), 93 deletions(-) diff --git a/board/cr50/board.h b/board/cr50/board.h index 8bb7416d02..07cc8cc49e 100644 --- a/board/cr50/board.h +++ b/board/cr50/board.h @@ -27,7 +27,7 @@ /* USB configuration */ #define CONFIG_USB #define CONFIG_USB_HID -#define CONFIG_USB_BLOB +#define CONFIG_USB_CONSOLE #define CONFIG_STREAM_USART #define CONFIG_STREAM_USB @@ -80,7 +80,7 @@ enum usb_strings { #endif /* !__ASSEMBLER__ */ /* USB interface indexes (use define rather than enum to expand them) */ -#define USB_IFACE_BLOB 0 +#define USB_IFACE_CONSOLE 0 #define USB_IFACE_HID 1 #define USB_IFACE_AP 2 #define USB_IFACE_EC 3 @@ -88,7 +88,7 @@ enum usb_strings { /* USB endpoint indexes (use define rather than enum to expand them) */ #define USB_EP_CONTROL 0 -#define USB_EP_BLOB 1 +#define USB_EP_CONSOLE 1 #define USB_EP_HID 2 #define USB_EP_AP 3 #define USB_EP_EC 4 diff --git a/chip/g/usb_console.c b/chip/g/usb_console.c index 1414ddbe1e..8195b5b433 100644 --- a/chip/g/usb_console.c +++ b/chip/g/usb_console.c @@ -8,6 +8,7 @@ #include "console.h" #include "link_defs.h" #include "printf.h" +#include "queue.h" #include "registers.h" #include "task.h" #include "timer.h" @@ -16,14 +17,7 @@ /* Console output macro */ #define CPRINTF(format, args...) cprintf(CC_USB, format, ## args) - #define USB_CONSOLE_TIMEOUT_US (30 * MSEC) -#define USB_CONSOLE_RX_BUF_SIZE 16 -#define RX_BUF_NEXT(i) (((i) + 1) & (USB_CONSOLE_RX_BUF_SIZE - 1)) - -static volatile char rx_buf[USB_CONSOLE_RX_BUF_SIZE]; -static volatile int rx_buf_head; -static volatile int rx_buf_tail; static int last_tx_ok = 1; @@ -68,35 +62,144 @@ static uint8_t ep_buf_rx[USB_MAX_PACKET_SIZE]; static struct g_usb_desc ep_out_desc; static struct g_usb_desc ep_in_desc; -static void con_ep_tx(void) +static struct queue const tx_q = QUEUE_NULL(256, uint8_t); +static struct queue const rx_q = QUEUE_NULL(USB_MAX_PACKET_SIZE, uint8_t); + + +/* Let the USB HW IN-to-host FIFO transmit some bytes */ +static void usb_enable_tx(int len) { - /* clear IT */ - GR_USB_DIEPINT(USB_EP_CONSOLE) = 0xffffffff; + ep_in_desc.flags = DIEPDMA_LAST | DIEPDMA_BS_HOST_RDY | DIEPDMA_IOC | + DIEPDMA_TXBYTES(len); + GR_USB_DIEPCTL(USB_EP_CONSOLE) |= DXEPCTL_CNAK | DXEPCTL_EPENA; } -static void con_ep_rx(void) +/* Let the USB HW OUT-from-host FIFO receive some bytes */ +static void usb_enable_rx(int len) { - int i; - int rx_size = is_readonly ? 0 : USB_MAX_PACKET_SIZE - - (ep_out_desc.flags & DOEPDMA_RXBYTES_MASK); - - for (i = 0; i < rx_size; i++) { - int rx_buf_next = RX_BUF_NEXT(rx_buf_head); - if (rx_buf_next != rx_buf_tail) { - rx_buf[rx_buf_head] = ep_buf_rx[i]; - rx_buf_head = rx_buf_next; - } - } - - ep_out_desc.flags = DOEPDMA_RXBYTES(USB_MAX_PACKET_SIZE) | + ep_out_desc.flags = DOEPDMA_RXBYTES(len) | DOEPDMA_LAST | DOEPDMA_BS_HOST_RDY | DOEPDMA_IOC; GR_USB_DOEPCTL(USB_EP_CONSOLE) |= DXEPCTL_CNAK | DXEPCTL_EPENA; - /* clear IT */ +} + +/* True if the HW Rx/OUT FIFO has bytes for us. */ +static inline int rx_fifo_is_ready(void) +{ + return (ep_out_desc.flags & DOEPDMA_BS_MASK) == DOEPDMA_BS_DMA_DONE; +} + +/* + * This function tries to shove new bytes from the USB host into the queue for + * consumption elsewhere. It is invoked either by a HW interrupt (telling us we + * have new bytes from the USB host), or by whoever is reading bytes out of the + * other end of the queue (telling us that there's now more room in the queue + * if we still have bytes to shove in there). + */ +static void rx_fifo_handler(void) +{ + /* + * The HW FIFO buffer (ep_buf_rx) is always filled from [0] by the + * hardware. The rx_in_fifo variable counts how many bytes of that + * buffer are actually valid, and is calculated from the HW DMA + * descriptor table. The descriptor is updated by the hardware, and it + * and ep_buf_rx remains valid and unchanged until software tells the + * the hardware engine to accept more input. + */ + int rx_in_fifo, rx_left; + + /* + * The rx_handled variable tracks how many of the bytes in the HW FIFO + * we've copied into the incoming queue. The queue may not accept all + * of them at once, so we have to keep track of where we are so that + * the next time this function is called we can try to shove the rest + * of the HW FIFO bytes into the queue. + */ + static int rx_handled; + + /* If the HW FIFO isn't ready, then we're waiting for more bytes */ + if (!rx_fifo_is_ready()) + return; + + /* + * How many of the HW FIFO bytes have we not yet handled? We need to + * know both where we are in the buffer and how many bytes we haven't + * yet enqueued. One can be calculated from the other as long as we + * know rx_in_fifo, but we need at least one static variable. + */ + rx_in_fifo = USB_MAX_PACKET_SIZE + - (ep_out_desc.flags & DOEPDMA_RXBYTES_MASK); + rx_left = rx_in_fifo - rx_handled; + + /* If we have some, try to shove them into the queue */ + if (rx_left) { + size_t added = QUEUE_ADD_UNITS(&rx_q, ep_buf_rx + rx_handled, + rx_left); + rx_handled += added; + rx_left -= added; + } + + if (rx_handled) + task_wake(TASK_ID_CONSOLE); + /* + * When we've handled all the bytes in the queue ("rx_in_fifo == + * rx_handled" and "rx_left == 0" indicate the same thing), we can + * reenable the USB HW to go fetch more. + */ + if (!rx_left) { + rx_handled = 0; + usb_enable_rx(USB_MAX_PACKET_SIZE); + } +} +DECLARE_DEFERRED(rx_fifo_handler); + +/* Rx/OUT interrupt handler */ +static void con_ep_rx(void) +{ + /* Wake up the Rx FIFO handler */ + hook_call_deferred(rx_fifo_handler, 0); + + /* clear the RX/OUT interrupts */ GR_USB_DOEPINT(USB_EP_CONSOLE) = 0xffffffff; +} +/* True if the Tx/IN FIFO can take some bytes from us. */ +static inline int tx_fifo_is_ready(void) +{ + uint32_t status = ep_in_desc.flags & DIEPDMA_BS_MASK; + return status == DIEPDMA_BS_DMA_DONE || status == DIEPDMA_BS_HOST_BSY; +} + +/* Try to send some bytes to the host */ +static void tx_fifo_handler(void) +{ + size_t count; + + if (!is_reset) + return; + + /* If the HW FIFO isn't ready, then we can't do anything right now. */ + if (!tx_fifo_is_ready()) + return; + + count = QUEUE_REMOVE_UNITS(&tx_q, ep_buf_tx, USB_MAX_PACKET_SIZE); + if (count) + usb_enable_tx(count); +} +DECLARE_DEFERRED(tx_fifo_handler); + +static void handle_output(void) +{ + /* Wake up the Tx FIFO handler */ + hook_call_deferred(tx_fifo_handler, 0); +} - /* wake-up the console task */ - if (!is_readonly) - console_has_input(); +/* Tx/IN interrupt handler */ +static void con_ep_tx(void) +{ + /* Wake up the Tx FIFO handler */ + hook_call_deferred(tx_fifo_handler, 0); + + /* clear the Tx/IN interrupts */ + GR_USB_DIEPINT(USB_EP_CONSOLE) = 0xffffffff; } static void ep_reset(void) @@ -114,50 +217,27 @@ static void ep_reset(void) GR_USB_DIEPCTL(USB_EP_CONSOLE) = DXEPCTL_MPS(64) | DXEPCTL_USBACTEP | DXEPCTL_EPTYPE_BULK | DXEPCTL_TXFNUM(USB_EP_CONSOLE); - GR_USB_DAINTMSK |= (1< 63) - return 1; - - ep_buf_tx[*tx_idx] = c; - (*tx_idx)++; - return 0; + /* Flush any queued data */ + hook_call_deferred(tx_fifo_handler, 0); + hook_call_deferred(rx_fifo_handler, 0); } -static void usb_enable_tx(int len) -{ - if (!is_enabled) - return; - - ep_in_desc.flags = DIEPDMA_LAST | DIEPDMA_BS_HOST_RDY | DIEPDMA_IOC | - DIEPDMA_TXBYTES(len); - GR_USB_DIEPCTL(USB_EP_CONSOLE) |= DXEPCTL_CNAK | DXEPCTL_EPENA; -} -static inline int usb_console_tx_valid(void) -{ - return (ep_in_desc.flags & DIEPDMA_BS_MASK) == DIEPDMA_BS_DMA_DONE; -} +USB_DECLARE_EP(USB_EP_CONSOLE, con_ep_tx, con_ep_rx, ep_reset); static int usb_wait_console(void) { timestamp_t deadline = get_time(); int wait_time_us = 1; + if (!is_enabled || !tx_fifo_is_ready()) + return EC_SUCCESS; + deadline.val += USB_CONSOLE_TIMEOUT_US; /* @@ -168,7 +248,7 @@ static int usb_wait_console(void) * we should wait so that we don't clobber the buffer. */ if (last_tx_ok) { - while (usb_console_tx_valid() || !is_reset) { + while (queue_space(&tx_q) < USB_MAX_PACKET_SIZE || !is_reset) { if (timestamp_expired(deadline, NULL) || in_interrupt_context()) { last_tx_ok = 0; @@ -183,10 +263,21 @@ static int usb_wait_console(void) return EC_SUCCESS; } else { - last_tx_ok = !usb_console_tx_valid(); + last_tx_ok = queue_space(&tx_q); return EC_SUCCESS; } } +static int __tx_char(void *context, int c) +{ + struct queue *state = + (struct queue *) context; + + if (c == '\n' && __tx_char(state, '\r')) + return 1; + + QUEUE_ADD_UNITS(state, &c, 1); + return 0; +} /* * Public USB console implementation below. @@ -195,64 +286,68 @@ int usb_getc(void) { int c; - if (rx_buf_tail == rx_buf_head) - return -1; - if (!is_enabled) return -1; - c = rx_buf[rx_buf_tail]; - rx_buf_tail = RX_BUF_NEXT(rx_buf_tail); - return c; + if (QUEUE_REMOVE_UNITS(&rx_q, &c, 1)) + return c; + return -1; } int usb_putc(int c) { - int ret; - int tx_idx = 0; + int ret = usb_wait_console(); - ret = usb_wait_console(); if (ret) return ret; - ret = __tx_char(&tx_idx, c); - usb_enable_tx(tx_idx); - - return ret; + ret = QUEUE_ADD_UNITS(&tx_q, &c, 1); + if (ret) + handle_output(); + return ret ? EC_SUCCESS : EC_ERROR_OVERFLOW; } int usb_puts(const char *outstr) { int ret; - int tx_idx = 0; + struct queue state; - ret = usb_wait_console(); + if (is_readonly) + return EC_SUCCESS; + + ret = usb_wait_console(); if (ret) return ret; - /* Put all characters in the output buffer */ - while (*outstr) { - if (__tx_char(&tx_idx, *outstr++) != 0) + state = tx_q; + while (*outstr) + if (__tx_char(&state, *outstr++)) break; - } - usb_enable_tx(tx_idx); + if (queue_count(&state)) + handle_output(); - /* Successful if we consumed all output */ return *outstr ? EC_ERROR_OVERFLOW : EC_SUCCESS; } int usb_vprintf(const char *format, va_list args) { int ret; - int tx_idx = 0; + struct queue state; + + if (is_readonly) + return EC_SUCCESS; + + ret = usb_wait_console(); + if (ret) + return ret; + + state = tx_q; + ret = vfnprintf(__tx_char, &state, format, args); + + if (queue_count(&state)) + handle_output(); - ret = vfnprintf(__tx_char, &tx_idx, format, args); - if (!ret && is_reset) { - ret = usb_wait_console(); - if (!ret) - usb_enable_tx(tx_idx); - } return ret; } -- cgit v1.2.1