3c6e5dbd70
I doubt anybody will ask for the configuration and request that 0 bytes be returned, but AFAICS that's legal, so let's support it. Should have no effect on ChipIdea since it knows not to send more data than requested by the host. BRANCH=none BUG=none TEST=none Change-Id: Ibfe57b593015fa5e0381c45ff9e39c3f912b4d4d Signed-off-by: Patrick Georgi <pgeorgi@chromium.org> Original-Commit-Id: 45555e929d9d07dbb58ecfd18333f26375a0e3d7 Original-Change-Id: I7432772a1812c6f52c2b1688ee4c6f67d02ccf28 Original-Signed-off-by: Patrick Georgi <pgeorgi@chromium.org> Original-Reviewed-on: https://chromium-review.googlesource.com/258064 Original-Reviewed-by: Furquan Shaikh <furquan@chromium.org> Reviewed-on: http://review.coreboot.org/9790 Tested-by: build bot (Jenkins) Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
354 lines
9.7 KiB
C
354 lines
9.7 KiB
C
/*
|
|
* 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))
|
|
|
|
/* determine if an additional zero length packet is necessary for
|
|
* a transfer */
|
|
static unsigned int zlp(struct usbdev_ctrl *this, const int epnum,
|
|
const int len, const int explen)
|
|
{
|
|
const unsigned int mps = this->ep_mps[epnum][1];
|
|
|
|
/* zero length transfers are handled explicitly */
|
|
if (len == 0)
|
|
return 0;
|
|
/* host expects exactly the right amount, so no zlp necessary */
|
|
if (len == explen)
|
|
return 0;
|
|
/* last packet will be short -> host knows that transfer is over */
|
|
if ((len % mps) != 0)
|
|
return 0;
|
|
|
|
/* otherwise we need an extra zero length packet */
|
|
return 1;
|
|
}
|
|
|
|
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 cease_operation(struct usbdev_ctrl *this)
|
|
{
|
|
int i;
|
|
for (i = 1; i < 16; i++) {
|
|
/* disable endpoints */
|
|
this->halt_ep(this, i, 0);
|
|
this->halt_ep(this, i, 1);
|
|
}
|
|
|
|
}
|
|
|
|
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 */
|
|
cease_operation(this);
|
|
|
|
/* now enable all configured endpoints */
|
|
int epcount = iface->descriptor.bNumEndpoints;
|
|
int i;
|
|
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);
|
|
}
|
|
|
|
this->current_iface = iface;
|
|
|
|
// 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);
|
|
|
|
if (dr->wValue == 0)
|
|
cease_operation(this);
|
|
|
|
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);
|
|
|
|
this->current_config = config;
|
|
this->current_config_id = dr->wValue;
|
|
|
|
/* activate first interface */
|
|
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, min(1, dr->wLength),
|
|
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(this, 0, 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(this, 0, 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);
|
|
}
|
|
|