From ece4481cd10202d1bd5f819b9178697a81144e63 Mon Sep 17 00:00:00 2001 From: Anton Staaf Date: Tue, 2 Sep 2014 10:36:00 -0700 Subject: stm32-USB: Initial USB bulk endpoint stream driver This stream driver works like the USART stream driver but connects to two bulk USB endpoints. Signed-off-by: Anton Staaf BRANCH=None BUG=None TEST=make buildall -j Change-Id: I9cbd2e54a811d3e32c68a820f7ab5de693c29569 Reviewed-on: https://chromium-review.googlesource.com/216002 Reviewed-by: Vincent Palatin Tested-by: Anton Staaf Commit-Queue: Anton Staaf --- chip/stm32/build.mk | 1 + chip/stm32/usb-stream.c | 216 ++++++++++++++++++++++++++++++++++++++++++++++++ chip/stm32/usb-stream.h | 201 ++++++++++++++++++++++++++++++++++++++++++++ include/config.h | 4 + 4 files changed, 422 insertions(+) create mode 100644 chip/stm32/usb-stream.c create mode 100644 chip/stm32/usb-stream.h diff --git a/chip/stm32/build.mk b/chip/stm32/build.mk index 8c6b0cd2e6..5a360bd8a2 100644 --- a/chip/stm32/build.mk +++ b/chip/stm32/build.mk @@ -31,6 +31,7 @@ chip-$(CONFIG_COMMON_GPIO)+=gpio.o gpio-$(CHIP_FAMILY).o chip-$(CONFIG_COMMON_TIMER)+=hwtimer$(TIMER_TYPE).o chip-$(CONFIG_I2C)+=i2c-$(CHIP_FAMILY).o i2c.o chip-$(CONFIG_STREAM_USART)+=usart.o usart-$(CHIP_FAMILY).o +chip-$(CONFIG_STREAM_USB)+=usb-stream.o chip-$(CONFIG_WATCHDOG)+=watchdog.o chip-$(HAS_TASK_CONSOLE)+=uart.o chip-$(HAS_TASK_KEYSCAN)+=keyboard_raw.o diff --git a/chip/stm32/usb-stream.c b/chip/stm32/usb-stream.c new file mode 100644 index 0000000000..644ac88dbb --- /dev/null +++ b/chip/stm32/usb-stream.c @@ -0,0 +1,216 @@ +/* Copyright (c) 2014 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 "atomic.h" +#include "common.h" +#include "config.h" +#include "link_defs.h" +#include "printf.h" +#include "registers.h" +#include "task.h" +#include "timer.h" +#include "util.h" +#include "usb.h" +#include "usb-stream.h" + +/* + * The USB packet RAM is attached to the processor via the AHB2APB bridge. This + * bridge performs manipulations of read and write accesses as per the note in + * section 2.1 of RM0091. The upshot is that it is OK to read from the packet + * RAM using 8-bit or 16-bit accesses, but not 32-bit, and it is only really OK + * to write to the packet RAM using 16-bit accesses. Thus custom memcpy like + * routines need to be employed. Furthermore, reading from and writing to the + * RX and TX queues uses memcpy, which will try to do 32-bit accesses if it can. + * so we must read and write single bytes at a time and construct 16-bit + * accesses to the packet RAM. + * + * This could be improved by adding a set of operations on the queue to get + * a pointer and size of the largest contiguous free/full region, then that + * region could be operated on and a commit operation could be performed on + * the queue. + */ +static size_t rx_read(struct usb_stream_config const *config) +{ + size_t count = btable_ep[config->endpoint].rx_count & 0x3ff; + + if (count < queue_space(&config->rx)) { + usb_uint *buffer = config->rx_ram; + size_t i; + + for (i = 0; i < count / 2; i++) { + usb_uint word = *buffer++; + uint8_t lsb = (word >> 0) & 0xff; + uint8_t msb = (word >> 8) & 0xff; + + queue_add_unit(&config->rx, &lsb); + queue_add_unit(&config->rx, &msb); + } + + if (count & 1) { + usb_uint word = *buffer++; + uint8_t lsb = (word >> 0) & 0xff; + + queue_add_unit(&config->rx, &lsb); + } + + return count; + } + + return 0; +} + +static size_t tx_write(struct usb_stream_config const *config) +{ + usb_uint *buffer = config->tx_ram; + size_t count = MIN(USB_MAX_PACKET_SIZE, queue_count(&config->tx)); + size_t i; + + for (i = 0; i < count / 2; i++) { + uint8_t lsb; + uint8_t msb; + + queue_remove_unit(&config->tx, &lsb); + queue_remove_unit(&config->tx, &msb); + + *buffer++ = (msb << 8) | lsb; + } + + if (count & 1) { + uint8_t lsb; + + queue_remove_unit(&config->tx, &lsb); + + *buffer++ = lsb; + } + + btable_ep[config->endpoint].tx_count = count; + + return count; +} + +static size_t usb_read(struct in_stream const *stream, + uint8_t *buffer, + size_t count) +{ + struct usb_stream_config const *config = + DOWNCAST(stream, struct usb_stream_config, in); + + size_t read = QUEUE_REMOVE_UNITS(&config->rx, buffer, count); + + if (config->state->rx_waiting && rx_read(config)) { + config->state->rx_waiting = 0; + + STM32_TOGGLE_EP(config->endpoint, EP_RX_MASK, EP_RX_VALID, 0); + + /* + * Make sure that the reader of this queue knows that there is + * more to read. + */ + in_stream_ready(&config->in); + + /* + * If there is still space left in the callers buffer fill it + * up with the additional bytes just added to the queue. + */ + if (count - read > 0) + read += QUEUE_REMOVE_UNITS(&config->rx, + buffer + read, + count - read); + } + + return read; +} + +static int tx_valid(struct usb_stream_config const *config) +{ + return (STM32_USB_EP(config->endpoint) & EP_TX_MASK) == EP_TX_VALID; +} + +static size_t usb_write(struct out_stream const *stream, + uint8_t const *buffer, + size_t count) +{ + struct usb_stream_config const *config = + DOWNCAST(stream, struct usb_stream_config, out); + + size_t wrote = QUEUE_ADD_UNITS(&config->tx, buffer, count); + + /* + * If we are not currently in a valid transmission state and we had + * something for the TX buffer, then mark the TX endpoint as valid. + */ + if (!tx_valid(config) && tx_write(config)) + STM32_TOGGLE_EP(config->endpoint, EP_TX_MASK, EP_TX_VALID, 0); + + return wrote; +} + +static void usb_flush(struct out_stream const *stream) +{ + struct usb_stream_config const *config = + DOWNCAST(stream, struct usb_stream_config, out); + + while (tx_valid(config) || queue_count(&config->tx)) + ; +} + +struct in_stream_ops const usb_stream_in_stream_ops = { + .read = usb_read, +}; + +struct out_stream_ops const usb_stream_out_stream_ops = { + .write = usb_write, + .flush = usb_flush, +}; + +void usb_stream_tx(struct usb_stream_config const *config) +{ + if (tx_write(config)) + STM32_TOGGLE_EP(config->endpoint, EP_TX_MASK, EP_TX_VALID, 0); + else + STM32_TOGGLE_EP(config->endpoint, 0, 0, 0); + + out_stream_ready(&config->out); +} + +void usb_stream_rx(struct usb_stream_config const *config) +{ + if (rx_read(config)) { + /* + * RX packet consumed, mark the packet as VALID. + */ + STM32_TOGGLE_EP(config->endpoint, EP_RX_MASK, EP_RX_VALID, 0); + } else { + /* + * There is not enough space in the RX queue to receive this + * packet. Leave the RX endpoint in a NAK state, clear the + * interrupt, and indicate to the usb_read function that when + * there is enough space in the queue to hold it there is an + * RX packet waiting. + */ + config->state->rx_waiting = 1; + STM32_TOGGLE_EP(config->endpoint, 0, 0, 0); + } + + in_stream_ready(&config->in); +} + +void usb_stream_reset(struct usb_stream_config const *config) +{ + int i = config->endpoint; + + btable_ep[i].tx_addr = usb_sram_addr(config->tx_ram); + btable_ep[i].tx_count = 0; + + btable_ep[i].rx_addr = usb_sram_addr(config->rx_ram); + btable_ep[i].rx_count = 0x8000 | ((USB_MAX_PACKET_SIZE / 32 - 1) << 10); + + config->state->rx_waiting = 0; + + STM32_USB_EP(i) = ((i << 0) | /* Endpoint Addr*/ + (2 << 4) | /* TX NAK */ + (0 << 9) | /* Bulk EP */ + (3 << 12)); /* RX VALID */ +} diff --git a/chip/stm32/usb-stream.h b/chip/stm32/usb-stream.h new file mode 100644 index 0000000000..ae6735a281 --- /dev/null +++ b/chip/stm32/usb-stream.h @@ -0,0 +1,201 @@ +/* Copyright (c) 2014 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. + */ +#ifndef CHIP_STM32_USB_STREAM_H +#define CHIP_STM32_USB_STREAM_H + +/* STM32 USB STREAM driver for Chrome EC */ + +#include "compile_time_macros.h" +#include "in_stream.h" +#include "out_stream.h" +#include "queue.h" +#include "usb.h" + +#include + +/* + * Per-USB stream state stored in RAM. Zero initialization of this structure + * by the BSS initialization leaves it in a valid and correctly initialized + * state, so there is no need currently for a usb_stream_init style function. + * + * If this structure is changed to require non-zero initialization such a + * function should be added. + */ +struct usb_stream_state { + struct queue_state rx; + struct queue_state tx; + + /* + * Flag indicating that there is a full RX buffer in the USB packet RAM + * that we were not able to move into the RX queue because there was + * not enough room when the packet was initially received. The + * in_stream read operation checks this flag so that once there is + * room in the queue it can copy the RX buffer into the queue and + * restart USB reception by marking the RX buffer as VALID. + */ + int rx_waiting; +}; + +/* + * Compile time Per-USB stream configuration stored in flash. Instances of this + * structure are provided by the user of the USB stream. This structure binds + * together all information required to operate a USB stream. + */ +struct usb_stream_config { + /* + * Pointer to usb_stream_state structure. The state structure + * maintains per USB stream information (head and tail pointers for + * the queues for instance). + */ + struct usb_stream_state volatile *state; + + /* + * Endpoint index, and pointers to the USB packet RAM buffers. + */ + int endpoint; + + usb_uint *rx_ram; + usb_uint *tx_ram; + + /* + * RX and TX queue config. The state for the queue is stored + * separately in the usb_stream_state structure. + */ + struct queue rx; + struct queue tx; + + /* + * In and Out streams, these contain pointers to the virtual function + * tables that implement in and out streams. They can be used by any + * code that wants to read or write to a stream interface. + */ + struct in_stream in; + struct out_stream out; +}; + +/* + * These function tables are defined by the USB stream driver and are used to + * initialize the in and out streams in the usb_stream_config. + */ +extern struct in_stream_ops const usb_stream_in_stream_ops; +extern struct out_stream_ops const usb_stream_out_stream_ops; + +/* + * Convenience macro for defining USB streams and their associated state and + * buffers. + * + * NAME is used to construct the names of the queue buffers, trampoline + * functions, usb_stream_state struct, and usb_stream_config struct, the + * latter is just called NAME. + * + * INTERFACE is the index of the USB interface to associate with this + * stream. + * + * ENDPOINT is the index of the USB bulk endpoint used for receiving and + * transmitting bytes. + * + * RX_SIZE and TX_SIZE are the size in bytes of the RX and TX queue buffers + * respectively. + * + * RX_READY and TX_READY are the callback functions for the in and out streams. + * These functions are called when there are bytes to read or space for bytes + * to write respectively. + */ +#define USB_STREAM_CONFIG(NAME, \ + INTERFACE, \ + ENDPOINT, \ + RX_SIZE, \ + TX_SIZE, \ + RX_READY, \ + TX_READY) \ + BUILD_ASSERT(RX_SIZE >= USB_MAX_PACKET_SIZE); \ + BUILD_ASSERT(TX_SIZE >= USB_MAX_PACKET_SIZE); \ + static uint8_t CONCAT2(NAME, _rx_buffer)[RX_SIZE]; \ + static uint8_t CONCAT2(NAME, _tx_buffer)[TX_SIZE]; \ + static usb_uint CONCAT2(NAME, _ep_rx_buffer)[USB_MAX_PACKET_SIZE / 2] __usb_ram; \ + static usb_uint CONCAT2(NAME, _ep_tx_buffer)[USB_MAX_PACKET_SIZE / 2] __usb_ram; \ + static struct usb_stream_state CONCAT2(NAME, _state); \ + struct usb_stream_config const NAME = { \ + .state = &CONCAT2(NAME, _state), \ + .endpoint = ENDPOINT, \ + .rx_ram = CONCAT2(NAME, _ep_rx_buffer), \ + .tx_ram = CONCAT2(NAME, _ep_tx_buffer), \ + .rx = { \ + .state = &CONCAT2(NAME, _state.rx), \ + .buffer_units = RX_SIZE, \ + .unit_bytes = 1, \ + .buffer = CONCAT2(NAME, _rx_buffer), \ + }, \ + .tx = { \ + .state = &CONCAT2(NAME, _state.tx), \ + .buffer_units = TX_SIZE, \ + .unit_bytes = 1, \ + .buffer = CONCAT2(NAME, _tx_buffer), \ + }, \ + .in = { \ + .ready = RX_READY, \ + .ops = &usb_stream_in_stream_ops, \ + }, \ + .out = { \ + .ready = TX_READY, \ + .ops = &usb_stream_out_stream_ops, \ + }, \ + }; \ + const struct usb_interface_descriptor \ + USB_IFACE_DESC(INTERFACE) = { \ + .bLength = USB_DT_INTERFACE_SIZE, \ + .bDescriptorType = USB_DT_INTERFACE, \ + .bInterfaceNumber = INTERFACE, \ + .bAlternateSetting = 0, \ + .bNumEndpoints = 2, \ + .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \ + .bInterfaceSubClass = 0, \ + .bInterfaceProtocol = 0, \ + .iInterface = 0, \ + }; \ + const struct usb_endpoint_descriptor \ + USB_EP_DESC(INTERFACE, 0) = { \ + .bLength = USB_DT_ENDPOINT_SIZE, \ + .bDescriptorType = USB_DT_ENDPOINT, \ + .bEndpointAddress = 0x80 | ENDPOINT, \ + .bmAttributes = 0x02 /* Bulk IN */, \ + .wMaxPacketSize = USB_MAX_PACKET_SIZE, \ + .bInterval = 10, \ + }; \ + const struct usb_endpoint_descriptor \ + USB_EP_DESC(INTERFACE, 1) = { \ + .bLength = USB_DT_ENDPOINT_SIZE, \ + .bDescriptorType = USB_DT_ENDPOINT, \ + .bEndpointAddress = ENDPOINT, \ + .bmAttributes = 0x02 /* Bulk OUT */, \ + .wMaxPacketSize = USB_MAX_PACKET_SIZE, \ + .bInterval = 0, \ + }; \ + static void CONCAT2(NAME, _ep_tx)(void) \ + { \ + usb_stream_tx(&NAME); \ + } \ + static void CONCAT2(NAME, _ep_rx)(void) \ + { \ + usb_stream_rx(&NAME); \ + } \ + static void CONCAT2(NAME, _ep_reset)(void) \ + { \ + usb_stream_reset(&NAME); \ + } \ + USB_DECLARE_EP(ENDPOINT, \ + CONCAT2(NAME, _ep_tx), \ + CONCAT2(NAME, _ep_rx), \ + CONCAT2(NAME, _ep_reset)); + +/* + * These functions are used by the trampoline functions defined above to + * connect USB endpoint events with the generic USB stream driver. + */ +void usb_stream_tx(struct usb_stream_config const *config); +void usb_stream_rx(struct usb_stream_config const *config); +void usb_stream_reset(struct usb_stream_config const *config); + +#endif /* CHIP_STM32_USB_STREAM_H */ diff --git a/include/config.h b/include/config.h index 5ae76fa5dd..ffd95fe0f2 100644 --- a/include/config.h +++ b/include/config.h @@ -924,6 +924,10 @@ #undef CONFIG_STREAM_USART3 #undef CONFIG_STREAM_USART4 +/*****************************************************************************/ +/* USB stream config */ +#undef CONFIG_STREAM_USB + /*****************************************************************************/ /* UART config */ -- cgit v1.2.1