summaryrefslogtreecommitdiff
path: root/payloads/libpayload/drivers/udc/udc.c
diff options
context:
space:
mode:
Diffstat (limited to 'payloads/libpayload/drivers/udc/udc.c')
-rw-r--r--payloads/libpayload/drivers/udc/udc.c327
1 files changed, 327 insertions, 0 deletions
diff --git a/payloads/libpayload/drivers/udc/udc.c b/payloads/libpayload/drivers/udc/udc.c
new file mode 100644
index 0000000000..cdc2b29f95
--- /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);
+}
+