libpayload: Add USB device mode driver

Add a framework for USB device mode controllers
and a driver for the ChipIdea controller which
is part of the tegra platform.

TODO:
- fix USB detach/attach
- implement zero length packet handling properly

BUG=chrome-os-partner:35861
TEST=none
BRANCH=none

Change-Id: I8defeea78b5a3bdbf9c1b1222c2702eaf3256b81
Signed-off-by: Patrick Georgi <pgeorgi@chromium.org>
Original-Commit-Id: 542332291880c4026a05a960ceb91d37891ee018
Original-Change-Id: Ib4068d201dd63ebeda80157bd3130f3059919cdd
Original-Signed-off-by: Patrick Georgi <pgeorgi@chromium.org>
Original-Reviewed-on: https://chromium-review.googlesource.com/243272
Original-Reviewed-by: Aaron Durbin <adurbin@chromium.org>
Reviewed-on: http://review.coreboot.org/8756
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
diff --git a/payloads/libpayload/drivers/udc/udc.c b/payloads/libpayload/drivers/udc/udc.c
new file mode 100644
index 0000000..cdc2b29
--- /dev/null
+++ b/payloads/libpayload/drivers/udc/udc.c
@@ -0,0 +1,327 @@
+/*
+ * This file is part of the libpayload project.
+ *
+ * Copyright (C) 2015 Google Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <libpayload.h>
+#include <arch/cache.h>
+#include <assert.h>
+#include <endian.h>
+#include <queue.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <usb/usb.h>
+
+#include <udc/udc.h>
+
+#ifdef DEBUG
+#define debug(x...) printf(x)
+#else
+#define debug(x...) do {} while (0)
+#endif
+
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+
+// TODO: make this right
+#define ZLP(len, explen) 0
+
+static struct usbdev_configuration *fetch_config(struct usbdev_ctrl *this,
+	int id)
+{
+	struct usbdev_configuration *config;
+	SLIST_FOREACH(config, &this->configs, list) {
+		debug("checking descriptor %d\n",
+			config->descriptor.bConfigurationValue);
+		if (config->descriptor.bConfigurationValue == id)
+			return config;
+	}
+	return NULL;
+}
+
+static void enable_interface(struct usbdev_ctrl *this, int iface_num)
+{
+	struct usbdev_configuration *config = this->current_config;
+	struct usbdev_interface *iface = &config->interfaces[iface_num];
+
+	/* first: shut down all endpoints except EP0 */
+	int i;
+	for (i = 1; i < 16; i++) {
+		/* disable endpoints */
+		this->halt_ep(this, i, 0);
+		this->halt_ep(this, i, 1);
+	}
+
+	/* now enable all configured endpoints */
+	int epcount = iface->descriptor.bNumEndpoints;
+	for (i = 0; i < epcount; i++) {
+		int ep = iface->eps[i].bEndpointAddress;
+		int mps = iface->eps[i].wMaxPacketSize;
+		int in_dir = 0;
+		if (ep & 0x80) {
+			in_dir = 1;
+			ep &= 0x7f;
+		}
+		int ep_type = iface->eps[i].bmAttributes & 0x3;
+		this->start_ep(this, ep, in_dir, ep_type, mps);
+	}
+
+	// gadget specific configuration
+	if (iface->init)
+		iface->init(this);
+}
+
+/**
+ * handle default control transfers on EP 0
+ *
+ * returns 1 if transfer was handled
+ */
+static int setup_ep0(struct usbdev_ctrl *this, dev_req_t *dr)
+{
+	if ((dr->bmRequestType == 0x00) &&
+	    (dr->bRequest == SET_ADDRESS)) {
+		this->set_address(this, dr->wValue & 0x7f);
+
+		/* status phase IN */
+		this->enqueue_packet(this, 0, 1, NULL, 0, 0, 0);
+		return 1;
+	} else
+	if ((dr->bmRequestType == 0x00) &&
+	    (dr->bRequest == SET_CONFIGURATION)) {
+		struct usbdev_configuration *config =
+			fetch_config(this, dr->wValue);
+
+		this->current_config = config;
+		this->current_config_id = dr->wValue;
+
+		if (dr->wValue == 0)
+			// TODO: reset device
+			return 1;
+
+		if (config == NULL) {
+			this->stall(this, 0, 0, 1);
+			this->stall(this, 0, 1, 1);
+			return 1;
+		}
+
+		/* status phase IN */
+		this->enqueue_packet(this, 0, 1, NULL, 0, 0, 0);
+
+		/* automatically configure endpoints in interface 0 */
+		enable_interface(this, 0);
+		this->initialized = 1;
+		return 1;
+	} else
+	if ((dr->bmRequestType == 0x80) &&
+	    (dr->bRequest == GET_CONFIGURATION)) {
+		unsigned char *res = dma_malloc(1);
+		res[0] = this->current_config_id;
+
+		/* data phase IN */
+		this->enqueue_packet(this, 0, 1, res, 1, 0, 1);
+
+		// status phase OUT
+		this->enqueue_packet(this, 0, 0, NULL, 0, 0, 0);
+		return 1;
+	} else
+	// ENDPOINT_HALT
+	if ((dr->bmRequestType == 0x02) && // endpoint
+	    (dr->bRequest == CLEAR_FEATURE) &&
+	    (dr->wValue == 0)) {
+		int ep = dr->wIndex;
+		/* clear STALL */
+		this->stall(this, ep & 0xf, ep & 0x80, 0);
+
+		/* status phase IN */
+		this->enqueue_packet(this, 0, 1, NULL, 0, 0, 0);
+		return 1;
+	} else
+	// ENDPOINT_HALT
+	if ((dr->bmRequestType == 0x02) && // endpoint
+	    (dr->bRequest == SET_FEATURE) &&
+	    (dr->wValue == 0)) {
+		int ep = dr->wIndex;
+		/* set STALL */
+		this->stall(this, ep & 0xf, ep & 0x80, 1);
+
+		/* status phase IN */
+		this->enqueue_packet(this, 0, 1, NULL, 0, 0, 0);
+		return 1;
+	} else
+	// DEVICE_REMOTE_WAKEUP
+	if ((dr->bmRequestType == 0x00) &&
+	    (dr->bRequest == CLEAR_FEATURE) &&
+	    (dr->wValue == 1)) {
+		this->remote_wakeup = 0;
+
+		/* status phase IN */
+		this->enqueue_packet(this, 0, 1, NULL, 0, 0, 0);
+		return 1;
+	} else
+	// DEVICE_REMOTE_WAKEUP
+	if ((dr->bmRequestType == 0x00) &&
+	    (dr->bRequest == SET_FEATURE) &&
+	    (dr->wValue == 1)) {
+		this->remote_wakeup = 1;
+
+		/* status phase IN */
+		this->enqueue_packet(this, 0, 1, NULL, 0, 0, 0);
+		return 1;
+	} else
+	if ((dr->bmRequestType == 0x82) && // endpoint
+	    (dr->bRequest == GET_STATUS)) {
+		unsigned char *res = dma_malloc(2);
+		int ep = dr->wIndex;
+		/* is EP halted? */
+		res[0] = this->ep_halted[ep & 0xf][(ep & 0x80) ? 1 : 0];
+		res[1] = 0;
+
+		/* data phase IN */
+		this->enqueue_packet(this, 0, 1, res,
+			min(2, dr->wLength), 0, 1);
+
+		// status phase OUT
+		this->enqueue_packet(this, 0, 0, NULL, 0, 0, 0);
+		return 1;
+	} else
+	if ((dr->bmRequestType == 0x80) &&
+	    (dr->bRequest == GET_STATUS)) {
+		unsigned char *res = dma_malloc(2);
+		res[0] = 1; // self powered
+		if (this->remote_wakeup)
+			res[0] |= 2;
+
+		res[1] = 0;
+
+		/* data phase IN */
+		this->enqueue_packet(this, 0, 1, res,
+			min(2, dr->wLength), 0, 1);
+
+		// status phase OUT
+		this->enqueue_packet(this, 0, 0, NULL, 0, 0, 0);
+		return 1;
+	} else
+	if ((dr->bmRequestType == 0x80) &&
+	    (dr->bRequest == GET_DESCRIPTOR) &&
+	    ((dr->wValue & 0xff00) == 0x0200)) {
+		int i, j;
+		/* config descriptor #id
+		 * since config = 0 is undefined, but descriptors
+		 * should start at 0, add 1 to have them match up.
+		 */
+		int id = (dr->wValue & 0xff) + 1;
+		struct usbdev_configuration *config = fetch_config(this, id);
+		if (config == NULL) {
+			this->stall(this, 0, 0, 1);
+			this->stall(this, 0, 1, 1);
+			return 1;
+		}
+		debug("descriptor found, should be %d bytes\n",
+			config->descriptor.wTotalLength);
+
+		uint8_t *data = dma_malloc(config->descriptor.wTotalLength);
+		uint8_t *head = data;
+
+		memcpy(head, &config->descriptor,
+			sizeof(configuration_descriptor_t));
+		head += sizeof(configuration_descriptor_t);
+
+		for (i = 0; i < config->descriptor.bNumInterfaces; i++) {
+			memcpy(head, &config->interfaces[i].descriptor,
+				sizeof(interface_descriptor_t));
+			head += sizeof(interface_descriptor_t);
+			for (j = 0;
+			     j < config->interfaces[i].descriptor.bNumEndpoints;
+			     j++) {
+				memcpy(head, &config->interfaces[i].eps[j],
+					sizeof(endpoint_descriptor_t));
+				head += sizeof(endpoint_descriptor_t);
+			}
+		}
+		int size = config->descriptor.wTotalLength;
+		assert((head - data) == config->descriptor.wTotalLength);
+
+		/* data phase IN */
+		this->enqueue_packet(this, 0, 1, data,
+			min(size, dr->wLength),
+			ZLP(size, dr->wLength), 1);
+
+		/* status phase OUT */
+		this->enqueue_packet(this, 0, 0, NULL, 0, 0, 0);
+		return 1;
+	} else
+	if ((dr->bmRequestType == 0x80) &&
+	    (dr->bRequest == GET_DESCRIPTOR) &&
+	    ((dr->wValue & 0xff00) == 0x0100)) {
+		device_descriptor_t *dd = dma_malloc(sizeof(*dd));
+		memcpy(dd, &this->device_descriptor, sizeof(*dd));
+		dd->bNumConfigurations = this->config_count;
+
+		/* data phase IN */
+		this->enqueue_packet(this, 0, 1, (void *)dd,
+			min(sizeof(*dd), dr->wLength),
+			ZLP(sizeof(*dd), dr->wLength), 1);
+
+		/* status phase OUT */
+		this->enqueue_packet(this, 0, 0, NULL, 0, 0, 0);
+		return 1;
+	}
+	return 0;
+}
+
+void udc_add_gadget(struct usbdev_ctrl *this,
+	struct usbdev_configuration *config)
+{
+	int i, size;
+	SLIST_INSERT_HEAD(&this->configs, config, list);
+
+	size = sizeof(configuration_descriptor_t);
+
+	for (i = 0; i < config->descriptor.bNumInterfaces; i++) {
+		size += sizeof(config->interfaces[i].descriptor);
+		size += config->interfaces[i].descriptor.bNumEndpoints *
+			sizeof(endpoint_descriptor_t);
+	}
+	config->descriptor.wTotalLength = size;
+	config->descriptor.bConfigurationValue = ++this->config_count;
+}
+
+void udc_handle_setup(struct usbdev_ctrl *this, int ep, dev_req_t *dr)
+{
+	if ((ep == 0) && setup_ep0(this, dr))
+		return;
+
+	if (this->current_config &&
+	    this->current_config->interfaces[0].handle_setup &&
+	    this->current_config->interfaces[0].handle_setup(this, ep, dr))
+		return;
+
+	/* no successful SETUP transfer should end up here, report error */
+	this->halt_ep(this, ep, 0);
+	this->halt_ep(this, ep, 1);
+}
+