coreboot-kgpe-d16/payloads/libpayload/drivers/usb/xhci_devconf.c
Julius Werner d13e2c4ab7 libpayload: usb: Refactor USB enumeration to fix SuperSpeed devices
This patch represents a major overhaul of the USB enumeration code in
order to make it cleaner and much more robust to weird or malicious
devices. The main improvement is that it correctly parses the USB
descriptors even if there are unknown descriptors interspersed within,
which is perfectly legal and in particular present on all SuperSpeed
devices (due to the SuperSpeed Endpoint Companion Descriptor).

In addition, it gets rid of the really whacky and special cased
get_descriptor() function, which would read every descriptor twice
whether it made sense or not. The new code makes the callers allocate
descriptor memory and only read stuff twice when it's really necessary
(i.e. the device and configuration descriptors).

Finally, it also moves some more responsibilities into the
controller-specific set_address() function in order to make sure things
are initialized at the same stage for all controllers. In the new model
it initializes the device entry (which zeroes the endpoint array), sets
up endpoint 0 (including MPS), sets the device address and finally
returns the whole usbdev_t structure with that address correctly set.

Note that this should make SuperSpeed devices work, but SuperSpeed hubs
are a wholly different story and would require a custom hub driver
(since the hub descriptor and port status formats are different for USB
3.0 ports, and the whole issue about the same hub showing up as two
different devices on two different ports might present additional
challenges). The stack currently just issues a warning and refuses to
initialize this part of the hub, which means that 3.0 devices connected
through a 3.0 hub may not work correctly.

Change-Id: Ie0b82dca23b7a750658ccc1a85f9daae5fbc20e1
Signed-off-by: Julius Werner <jwerner@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/170666
Reviewed-by: Kees Cook <keescook@chromium.org>
(cherry picked from commit ecec80e062f7efe32a9a17479dcf8cb678a4a98b)
Signed-off-by: Isaac Christensen <isaac.christensen@se-eng.com>
Reviewed-on: http://review.coreboot.org/6780
Tested-by: build bot (Jenkins)
2014-09-04 01:59:15 +02:00

405 lines
11 KiB
C

/*
* This file is part of the libpayload project.
*
* Copyright (C) 2013 secunet Security Networks AG
*
* 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.
*/
//#define XHCI_SPEW_DEBUG
#include <arch/virtual.h>
#include <usb/usb.h>
#include "xhci_private.h"
static u32
xhci_gen_route(xhci_t *const xhci, const int hubport, const int hubaddr)
{
if (!hubaddr)
return 0;
u32 route_string = SC_GET(ROUTE, xhci->dev[hubaddr].ctx.slot);
int i;
for (i = 0; i < 20; i += 4) {
if (!(route_string & (0xf << i))) {
route_string |= (hubport & 0xf) << i;
break;
}
}
return route_string;
}
static int
xhci_get_rh_port(xhci_t *const xhci, const int hubport, const int hubaddr)
{
if (!hubaddr)
return hubport;
return SC_GET(RHPORT, xhci->dev[hubaddr].ctx.slot);
}
static int
xhci_get_tt(xhci_t *const xhci, const usb_speed speed,
const int hubport, const int hubaddr,
int *const tt, int *const tt_port)
{
if (!hubaddr)
return 0;
const slotctx_t *const slot = xhci->dev[hubaddr].ctx.slot;
if ((*tt = SC_GET(TTID, slot))) {
*tt_port = SC_GET(TTPORT, slot);
} else if (speed < HIGH_SPEED &&
SC_GET(SPEED1, slot) - 1 == HIGH_SPEED) {
*tt = hubaddr;
*tt_port = hubport;
}
return *tt != 0;
}
static inputctx_t *
xhci_make_inputctx(const size_t ctxsize)
{
int i;
const size_t size = (1 + NUM_EPS) * ctxsize;
inputctx_t *const ic = malloc(sizeof(*ic));
void *dma_buffer = dma_memalign(64, size);
if (!ic || !dma_buffer) {
free(ic);
free(dma_buffer);
return NULL;
}
memset(dma_buffer, 0, size);
ic->drop = dma_buffer + 0;
ic->add = dma_buffer + 4;
dma_buffer += ctxsize;
for (i = 0; i < NUM_EPS; i++, dma_buffer += ctxsize)
ic->dev.ep[i] = dma_buffer;
return ic;
}
usbdev_t *
xhci_set_address (hci_t *controller, usb_speed speed, int hubport, int hubaddr)
{
xhci_t *const xhci = XHCI_INST(controller);
const size_t ctxsize = CTXSIZE(xhci);
devinfo_t *di = NULL;
usbdev_t *dev = NULL;
int i;
inputctx_t *const ic = xhci_make_inputctx(ctxsize);
transfer_ring_t *const tr = malloc(sizeof(*tr));
if (tr)
tr->ring = xhci_align(16, TRANSFER_RING_SIZE * sizeof(trb_t));
if (!ic || !tr || !tr->ring) {
xhci_debug("Out of memory\n");
goto _free_return;
}
int slot_id;
int cc = xhci_cmd_enable_slot(xhci, &slot_id);
if (cc != CC_SUCCESS) {
xhci_debug("Enable slot failed: %d\n", cc);
goto _free_return;
} else {
xhci_debug("Enabled slot %d\n", slot_id);
}
di = &xhci->dev[slot_id];
void *dma_buffer = dma_memalign(64, NUM_EPS * ctxsize);
if (!dma_buffer)
goto _disable_return;
memset(dma_buffer, 0, NUM_EPS * ctxsize);
for (i = 0; i < NUM_EPS; i++, dma_buffer += ctxsize)
di->ctx.ep[i] = dma_buffer;
*ic->add = (1 << 0) /* Slot Context */ | (1 << 1) /* EP0 Context */ ;
SC_SET(ROUTE, ic->dev.slot, xhci_gen_route(xhci, hubport, hubaddr));
SC_SET(SPEED1, ic->dev.slot, speed + 1);
SC_SET(CTXENT, ic->dev.slot, 1); /* the endpoint 0 context */
SC_SET(RHPORT, ic->dev.slot, xhci_get_rh_port(xhci, hubport, hubaddr));
int tt, tt_port;
if (xhci_get_tt(xhci, speed, hubport, hubaddr, &tt, &tt_port)) {
xhci_debug("TT for %d: %d[%d]\n", slot_id, tt, tt_port);
SC_SET(MTT, ic->dev.slot, SC_GET(MTT, xhci->dev[tt].ctx.slot));
SC_SET(TTID, ic->dev.slot, tt);
SC_SET(TTPORT, ic->dev.slot, tt_port);
}
di->transfer_rings[1] = tr;
xhci_init_cycle_ring(tr, TRANSFER_RING_SIZE);
ic->dev.ep0->tr_dq_low = virt_to_phys(tr->ring);
ic->dev.ep0->tr_dq_high = 0;
EC_SET(TYPE, ic->dev.ep0, EP_CONTROL);
EC_SET(AVRTRB, ic->dev.ep0, 8);
EC_SET(MPS, ic->dev.ep0, 8);
EC_SET(CERR, ic->dev.ep0, 3);
EC_SET(DCS, ic->dev.ep0, 1);
xhci->dcbaa[slot_id] = virt_to_phys(di->ctx.raw);
cc = xhci_cmd_address_device(xhci, slot_id, ic);
if (cc != CC_SUCCESS) {
xhci_debug("Address device failed: %d\n", cc);
goto _disable_return;
} else {
xhci_debug("Addressed device %d (USB: %d)\n",
slot_id, SC_GET(UADDR, di->ctx.slot));
}
mdelay(SET_ADDRESS_MDELAY);
dev = init_device_entry(controller, slot_id);
if (!dev)
goto _disable_return;
dev->address = slot_id;
dev->hub = hubaddr;
dev->port = hubport;
dev->speed = speed;
dev->endpoints[0].dev = dev;
dev->endpoints[0].endpoint = 0;
dev->endpoints[0].toggle = 0;
dev->endpoints[0].direction = SETUP;
dev->endpoints[0].type = CONTROL;
u8 buf[8];
if (get_descriptor(dev, gen_bmRequestType(device_to_host, standard_type,
dev_recp), DT_DEV, 0, buf, sizeof(buf)) != sizeof(buf)) {
usb_debug("first get_descriptor(DT_DEV) failed\n");
goto _disable_return;
}
dev->endpoints[0].maxpacketsize = usb_decode_mps0(speed, buf[7]);
if (dev->endpoints[0].maxpacketsize != 8) {
memset((void *)ic->dev.ep0, 0x00, ctxsize);
*ic->add = (1 << 1); /* EP0 Context */
EC_SET(MPS, ic->dev.ep0, dev->endpoints[0].maxpacketsize);
cc = xhci_cmd_evaluate_context(xhci, slot_id, ic);
if (cc != CC_SUCCESS) {
xhci_debug("Context evaluation failed: %d\n", cc);
goto _disable_return;
}
}
goto _free_ic_return;
_disable_return:
xhci_cmd_disable_slot(xhci, slot_id);
xhci->dcbaa[slot_id] = 0;
usb_detach_device(controller, slot_id);
dev = NULL;
_free_return:
if (tr)
free((void *)tr->ring);
free(tr);
if (di)
free(di->ctx.raw);
free((void *)di);
_free_ic_return:
if (ic)
free(ic->raw);
free(ic);
return dev;
}
static int
xhci_finish_hub_config(usbdev_t *const dev, inputctx_t *const ic)
{
hub_descriptor_t desc;
if (get_descriptor(dev, gen_bmRequestType(device_to_host, class_type,
dev_recp), 0x29, 0, &desc, sizeof(desc)) != sizeof(desc)) {
xhci_debug("Failed to fetch hub descriptor\n");
return COMMUNICATION_ERROR;
}
SC_SET(HUB, ic->dev.slot, 1);
SC_SET(MTT, ic->dev.slot, 0); /* No support for Multi-TT */
SC_SET(NPORTS, ic->dev.slot, desc.bNbrPorts);
if (dev->speed == HIGH_SPEED)
SC_SET(TTT, ic->dev.slot,
(desc.wHubCharacteristics >> 5) & 0x0003);
return 0;
}
static size_t
xhci_bound_interval(const endpoint_t *const ep)
{
if ( (ep->dev->speed == LOW_SPEED &&
(ep->type == ISOCHRONOUS ||
ep->type == INTERRUPT)) ||
(ep->dev->speed == FULL_SPEED &&
ep->type == INTERRUPT))
{
if (ep->interval < 3)
return 3;
else if (ep->interval > 11)
return 11;
else
return ep->interval;
} else {
if (ep->interval < 0)
return 0;
else if (ep->interval > 15)
return 15;
else
return ep->interval;
}
}
static int
xhci_finish_ep_config(const endpoint_t *const ep, inputctx_t *const ic)
{
xhci_t *const xhci = XHCI_INST(ep->dev->controller);
const int ep_id = xhci_ep_id(ep);
xhci_debug("ep_id: %d\n", ep_id);
if (ep_id <= 1 || 32 <= ep_id)
return DRIVER_ERROR;
transfer_ring_t *const tr = malloc(sizeof(*tr));
if (tr)
tr->ring = xhci_align(16, TRANSFER_RING_SIZE * sizeof(trb_t));
if (!tr || !tr->ring) {
free(tr);
xhci_debug("Out of memory\n");
return OUT_OF_MEMORY;
}
xhci->dev[ep->dev->address].transfer_rings[ep_id] = tr;
xhci_init_cycle_ring(tr, TRANSFER_RING_SIZE);
*ic->add |= (1 << ep_id);
if (SC_GET(CTXENT, ic->dev.slot) < ep_id)
SC_SET(CTXENT, ic->dev.slot, ep_id);
epctx_t *const epctx = ic->dev.ep[ep_id];
xhci_debug("Filling epctx (@%p)\n", epctx);
epctx->tr_dq_low = virt_to_phys(tr->ring);
epctx->tr_dq_high = 0;
EC_SET(INTVAL, epctx, xhci_bound_interval(ep));
EC_SET(CERR, epctx, 3);
EC_SET(TYPE, epctx, ep->type | ((ep->direction != OUT) << 2));
EC_SET(MPS, epctx, ep->maxpacketsize);
EC_SET(DCS, epctx, 1);
size_t avrtrb;
switch (ep->type) {
case BULK: case ISOCHRONOUS: avrtrb = 3 * 1024; break;
case INTERRUPT: avrtrb = 1024; break;
default: avrtrb = 8; break;
}
EC_SET(AVRTRB, epctx, avrtrb);
EC_SET(MXESIT, epctx, EC_GET(MPS, epctx) * EC_GET(MBS, epctx));
return 0;
}
int
xhci_finish_device_config(usbdev_t *const dev)
{
xhci_t *const xhci = XHCI_INST(dev->controller);
devinfo_t *const di = &xhci->dev[dev->address];
int i, ret = 0;
inputctx_t *const ic = xhci_make_inputctx(CTXSIZE(xhci));
if (!ic) {
xhci_debug("Out of memory\n");
return OUT_OF_MEMORY;
}
*ic->add = (1 << 0); /* Slot Context */
xhci_dump_slotctx(di->ctx.slot);
ic->dev.slot->f1 = di->ctx.slot->f1;
ic->dev.slot->f2 = di->ctx.slot->f2;
ic->dev.slot->f3 = di->ctx.slot->f3;
if (dev->descriptor->bDeviceClass == 0x09 && dev->speed < SUPER_SPEED) {
ret = xhci_finish_hub_config(dev, ic);
if (ret)
goto _free_return;
}
for (i = 1; i < dev->num_endp; ++i) {
ret = xhci_finish_ep_config(&dev->endpoints[i], ic);
if (ret)
goto _free_ep_ctx_return;
}
xhci_dump_inputctx(ic);
const int config_id = dev->configuration->bConfigurationValue;
xhci_debug("config_id: %d\n", config_id);
const int cc =
xhci_cmd_configure_endpoint(xhci, dev->address, config_id, ic);
if (cc != CC_SUCCESS) {
xhci_debug("Configure endpoint failed: %d\n", cc);
ret = CONTROLLER_ERROR;
goto _free_ep_ctx_return;
} else {
xhci_debug("Endpoints configured\n");
}
goto _free_return;
_free_ep_ctx_return:
for (i = 2; i < 31; ++i) {
if (di->transfer_rings[i])
free((void *)di->transfer_rings[i]->ring);
free(di->transfer_rings[i]);
di->transfer_rings[i] = NULL;
}
_free_return:
free(ic->raw);
free(ic);
return ret;
}
void
xhci_destroy_dev(hci_t *const controller, const int slot_id)
{
xhci_t *const xhci = XHCI_INST(controller);
if (slot_id <= 0 || slot_id > xhci->max_slots_en)
return;
int i;
const int cc = xhci_cmd_disable_slot(xhci, slot_id);
if (cc != CC_SUCCESS)
xhci_debug("Failed to disable slot %d: %d\n", slot_id, cc);
devinfo_t *const di = &xhci->dev[slot_id];
for (i = 1; i < 31; ++i) {
if (di->transfer_rings[i])
free((void *)di->transfer_rings[i]->ring);
free(di->transfer_rings[i]);
free(di->interrupt_queues[i]);
}
xhci->dcbaa[slot_id] = 0;
}