Patrick Georgi ea0bdf2d54 libpayload: Add zero length packet support to UDC framework
Some IN transfers must be terminated by an empty packet
because otherwise the host wouldn't know.

The zlp() function determines this requirement in
accordance to USB rules: If the transfer's size is aligned
to the maximum packet size, and the host expects a larger
transfer, add the empty packet as a hint.

BRANCH=none
BUG=none
TEST=USB device mode still works

Change-Id: Ia69f3d017f72a3a0e0b21bac72fe97be184c7daa
Signed-off-by: Patrick Georgi <pgeorgi@chromium.org>
Original-Commit-Id: fd0e946e4948a74a9ed15a5eed6ce827b7672a56
Original-Change-Id: I8153cc5bd2ff1c88e383c1dbcddaf1bf72f9194c
Original-Signed-off-by: Patrick Georgi <pgeorgi@chromium.org>
Original-Reviewed-on: https://chromium-review.googlesource.com/250790
Original-Reviewed-by: Furquan Shaikh <furquan@chromium.org>
Reviewed-on: http://review.coreboot.org/9784
Tested-by: build bot (Jenkins)
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
2015-04-18 08:40:28 +02:00

348 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 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);
}
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);
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(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);
}