aboutsummaryrefslogtreecommitdiff
path: root/payloads/libpayload/drivers/usb/xhci.c
diff options
context:
space:
mode:
authorJulius Werner <jwerner@chromium.org>2013-09-03 17:15:31 -0700
committerIsaac Christensen <isaac.christensen@se-eng.com>2014-08-14 23:41:21 +0200
commit1f86434227beaf9806de86269f8b42eed817ae3a (patch)
tree25eb1420c05f75de4ca3c79eb4f9ed6c13b60c69 /payloads/libpayload/drivers/usb/xhci.c
parentd96541f3fc934fa27b800a07ccf0597bd5a80dd5 (diff)
libpayload: xhci: Make XHCI stack usable on ARM
This patch updates the libpayload XHCI stack to run on ARM CPUs (tested with the DWC3 controller on an Exynos5420). Firstly, it adds support for 64-byte Slot/Endpoint Context sizes. Since the existing context handling code represented the whole device context as a C struct (whose size has to be known at compile time), it was necessary to refactor the input and device context structures to consist of pointers to the actual contexts instead. Secondly, it moves all data structures that the xHC accesses through DMA to cache-coherent memory. With a similar rationale as in the ARM patches for EHCI, using explicit cache maintenance functions to correctly handle the actual transfer buffers in all cases is presumably impossible. Instead this patch also chooses to create a DMA bounce buffer in the XHCI stack where transfer buffers which are not already cache-coherent will be copied to/from. Change-Id: I14e82fffb43b4d52d687b65415f2e33920e088de Signed-off-by: Julius Werner <jwerner@chromium.org> Reviewed-on: https://chromium-review.googlesource.com/169453 Reviewed-by: Stefan Reinauer <reinauer@google.com> (cherry picked from commit 1fa9964063cce6cbd87ba68334806dde8aa2354c) Signed-off-by: Isaac Christensen <isaac.christensen@se-eng.com> Reviewed-on: http://review.coreboot.org/6643 Tested-by: build bot (Jenkins) Reviewed-by: Paul Menzel <paulepanter@users.sourceforge.net> Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
Diffstat (limited to 'payloads/libpayload/drivers/usb/xhci.c')
-rw-r--r--payloads/libpayload/drivers/usb/xhci.c126
1 files changed, 79 insertions, 47 deletions
diff --git a/payloads/libpayload/drivers/usb/xhci.c b/payloads/libpayload/drivers/usb/xhci.c
index 184a370936..4ab2fe3c34 100644
--- a/payloads/libpayload/drivers/usb/xhci.c
+++ b/payloads/libpayload/drivers/usb/xhci.c
@@ -62,7 +62,7 @@ xhci_align(const size_t min_align, const size_t size)
if (align < min_align)
align = min_align;
xhci_spew("Aligning %zu to %zu\n", size, align);
- return memalign(align, size);
+ return dma_memalign(align, size);
}
void
@@ -210,12 +210,7 @@ xhci_init (unsigned long physical_bar)
goto _free_xhci;
}
- xhci_debug("context size: %dB\n", xhci->capreg->csz ? 64 : 32);
- if (xhci->capreg->csz) {
- xhci_debug("Only 32B contexts are supported\n");
- goto _free_xhci;
- }
-
+ xhci_debug("context size: %dB\n", CTXSIZE(xhci));
xhci_debug("maxslots: 0x%02lx\n", xhci->capreg->MaxSlots);
xhci_debug("maxports: 0x%02lx\n", xhci->capreg->MaxPorts);
const unsigned pagesize = xhci->opreg->pagesize << 12;
@@ -226,13 +221,15 @@ xhci_init (unsigned long physical_bar)
* structures at first and can still chicken out easily if we run out
* of memory.
*/
- const size_t dcbaa_size = (xhci->capreg->MaxSlots + 1) * sizeof(u64);
- xhci->dcbaa = xhci_align(64, dcbaa_size);
- if (!xhci->dcbaa) {
+ xhci->max_slots_en = xhci->capreg->MaxSlots & CONFIG_LP_MASK_MaxSlotsEn;
+ xhci->dcbaa = xhci_align(64, (xhci->max_slots_en + 1) * sizeof(u64));
+ xhci->dev = malloc((xhci->max_slots_en + 1) * sizeof(*xhci->dev));
+ if (!xhci->dcbaa || !xhci->dev) {
xhci_debug("Out of memory\n");
goto _free_xhci;
}
- memset((void*)xhci->dcbaa, 0x00, dcbaa_size);
+ memset(xhci->dcbaa, 0x00, (xhci->max_slots_en + 1) * sizeof(u64));
+ memset(xhci->dev, 0x00, (xhci->max_slots_en + 1) * sizeof(*xhci->dev));
/*
* Let dcbaa[0] point to another array of pointers, sp_ptrs.
@@ -261,9 +258,17 @@ xhci_init (unsigned long physical_bar)
xhci->dcbaa[0] = virt_to_phys(xhci->sp_ptrs);
}
+ if (dma_initialized()) {
+ xhci->dma_buffer = dma_memalign(64 * 1024, DMA_SIZE);
+ if (!xhci->dma_buffer) {
+ xhci_debug("Not enough memory for DMA bounce buffer\n");
+ goto _free_xhci_structs;
+ }
+ }
+
/* Now start working on the hardware */
if (xhci_wait_ready(xhci))
- goto _free_xhci;
+ goto _free_xhci_structs;
/* TODO: Check if BIOS claims ownership (and hand over) */
@@ -290,6 +295,7 @@ _free_xhci:
free((void *)xhci->er.ring);
free((void *)xhci->cr.ring);
free(xhci->roothub);
+ free(xhci->dev);
free(xhci);
_free_controller:
detach_controller(controller);
@@ -344,8 +350,7 @@ xhci_reinit (hci_t *controller)
return;
/* Enable all available slots */
- xhci->opreg->config = xhci->capreg->MaxSlots & CONFIG_LP_MASK_MaxSlotsEn;
- xhci->max_slots_en = xhci->capreg->MaxSlots & CONFIG_LP_MASK_MaxSlotsEn;
+ xhci->opreg->config = xhci->max_slots_en;
/* Set DCBAA */
xhci->opreg->dcbaap_lo = virt_to_phys(xhci->dcbaa);
@@ -426,6 +431,7 @@ xhci_shutdown(hci_t *const controller)
}
free(xhci->sp_ptrs);
free(xhci->dcbaa);
+ free(xhci->dev);
free((void *)xhci->ev_ring_table);
free((void *)xhci->er.ring);
free((void *)xhci->cr.ring);
@@ -459,15 +465,15 @@ xhci_reset_endpoint(usbdev_t *const dev, endpoint_t *const ep,
const int clear_halt)
{
xhci_t *const xhci = XHCI_INST(dev->controller);
- devinfo_t *const di = DEVINFO_FROM_XHCI(xhci, dev->address);
const int slot_id = dev->address;
const int ep_id = ep ? xhci_ep_id(ep) : 1;
+ epctx_t *const epctx = xhci->dev[slot_id].ctx.ep[ep_id];
xhci_debug("Resetting ID %d EP %d (ep state: %d)\n",
- slot_id, ep_id, EC_GET(STATE, di->devctx.eps[ep_id]));
+ slot_id, ep_id, EC_GET(STATE, epctx));
/* Run Reset Endpoint Command if the EP is in Halted state */
- if (EC_GET(STATE, di->devctx.eps[ep_id]) == 2) {
+ if (EC_GET(STATE, epctx) == 2) {
const int cc = xhci_cmd_reset_endpoint(xhci, slot_id, ep_id);
if (cc != CC_SUCCESS) {
xhci_debug("Reset Endpoint Command failed: %d\n", cc);
@@ -486,9 +492,10 @@ xhci_reset_endpoint(usbdev_t *const dev, endpoint_t *const ep,
clear_stall(ep);
/* Reset transfer ring if the endpoint is in the right state */
- const unsigned ep_state = EC_GET(STATE, di->devctx.eps[ep_id]);
+ const unsigned ep_state = EC_GET(STATE, epctx);
if (ep_state == 3 || ep_state == 4) {
- transfer_ring_t *const tr = di->transfer_rings[ep_id];
+ transfer_ring_t *const tr =
+ xhci->dev[slot_id].transfer_rings[ep_id];
const int cc = xhci_cmd_set_tr_dq(xhci, slot_id, ep_id,
tr->ring, 1);
if (cc != CC_SUCCESS) {
@@ -499,7 +506,7 @@ xhci_reset_endpoint(usbdev_t *const dev, endpoint_t *const ep,
}
xhci_debug("Finished resetting ID %d EP %d (ep state: %d)\n",
- slot_id, ep_id, EC_GET(STATE, di->devctx.eps[ep_id]));
+ slot_id, ep_id, EC_GET(STATE, epctx));
return 0;
}
@@ -579,11 +586,12 @@ xhci_enqueue_td(transfer_ring_t *const tr, const int ep, const size_t mps,
static int
xhci_control(usbdev_t *const dev, const direction_t dir,
const int drlen, void *const devreq,
- const int dalen, unsigned char *const data)
+ const int dalen, unsigned char *const src)
{
+ unsigned char *data = src;
xhci_t *const xhci = XHCI_INST(dev->controller);
- devinfo_t *const di = DEVINFO_FROM_XHCI(xhci, dev->address);
- transfer_ring_t *const tr = di->transfer_rings[1];
+ epctx_t *const epctx = xhci->dev[dev->address].ctx.ep0;
+ transfer_ring_t *const tr = xhci->dev[dev->address].transfer_rings[1];
const size_t off = (size_t)data & 0xffff;
if ((off + dalen) > ((TRANSFER_RING_SIZE - 4) << 16)) {
@@ -592,12 +600,22 @@ xhci_control(usbdev_t *const dev, const direction_t dir,
}
/* Reset endpoint if it's halted */
- const unsigned ep_state = EC_GET(STATE, di->devctx.ep0);
+ const unsigned ep_state = EC_GET(STATE, epctx);
if (ep_state == 2 || ep_state == 4) {
if (xhci_reset_endpoint(dev, NULL, 0))
return -1;
}
+ if (dalen && !dma_coherent(src)) {
+ data = xhci->dma_buffer;
+ if (dalen > DMA_SIZE) {
+ xhci_debug("Control transfer too large: %d\n", dalen);
+ return -1;
+ }
+ if (dir == OUT)
+ memcpy(data, src, dalen);
+ }
+
/* Fill and enqueue setup TRB */
trb_t *const setup = tr->cur;
xhci_clear_trb(setup, tr->pcs);
@@ -614,7 +632,7 @@ xhci_control(usbdev_t *const dev, const direction_t dir,
/* Fill and enqueue data TRBs (if any) */
if (dalen) {
- const unsigned mps = EC_GET(MPS, di->devctx.ep0);
+ const unsigned mps = EC_GET(MPS, epctx);
const unsigned dt_dir = (dir == OUT) ? TRB_DIR_OUT : TRB_DIR_IN;
xhci_enqueue_td(tr, 1, mps, dalen, data, dt_dir);
}
@@ -650,28 +668,31 @@ xhci_control(usbdev_t *const dev, const direction_t dir,
" usbsts: 0x%08"PRIx32"\n",
i, n_stages, ret,
tr->ring, setup, status,
- ep_state, EC_GET(STATE, di->devctx.ep0),
+ ep_state, EC_GET(STATE, epctx),
xhci->opreg->usbsts);
return ret;
}
}
+ if (dir == IN && data != src)
+ memcpy(src, data, transferred);
return transferred;
}
/* finalize == 1: if data is of packet aligned size, add a zero length packet */
static int
-xhci_bulk(endpoint_t *const ep,
- const int size, u8 *const data,
+xhci_bulk(endpoint_t *const ep, const int size, u8 *const src,
const int finalize)
{
/* finalize: Hopefully the xHCI controller always does this.
We have no control over the packets. */
+ u8 *data = src;
xhci_t *const xhci = XHCI_INST(ep->dev->controller);
+ const int slot_id = ep->dev->address;
const int ep_id = xhci_ep_id(ep);
- devinfo_t *const di = DEVINFO_FROM_XHCI(xhci, ep->dev->address);
- transfer_ring_t *const tr = di->transfer_rings[ep_id];
+ epctx_t *const epctx = xhci->dev[slot_id].ctx.ep[ep_id];
+ transfer_ring_t *const tr = xhci->dev[slot_id].transfer_rings[ep_id];
const size_t off = (size_t)data & 0xffff;
if ((off + size) > ((TRANSFER_RING_SIZE - 2) << 16)) {
@@ -679,15 +700,25 @@ xhci_bulk(endpoint_t *const ep,
return -1;
}
+ if (!dma_coherent(src)) {
+ data = xhci->dma_buffer;
+ if (size > DMA_SIZE) {
+ xhci_debug("Bulk transfer too large: %d\n", size);
+ return -1;
+ }
+ if (ep->direction == OUT)
+ memcpy(data, src, size);
+ }
+
/* Reset endpoint if it's halted */
- const unsigned ep_state = EC_GET(STATE, di->devctx.eps[ep_id]);
+ const unsigned ep_state = EC_GET(STATE, epctx);
if (ep_state == 2 || ep_state == 4) {
if (xhci_reset_endpoint(ep->dev, ep, 0))
return -1;
}
/* Enqueue transfer and ring doorbell */
- const unsigned mps = EC_GET(MPS, di->devctx.eps[ep_id]);
+ const unsigned mps = EC_GET(MPS, epctx);
const unsigned dir = (ep->direction == OUT) ? TRB_DIR_OUT : TRB_DIR_IN;
xhci_enqueue_td(tr, ep_id, mps, size, data, dir);
xhci->dbreg[ep->dev->address] = ep_id;
@@ -706,11 +737,13 @@ xhci_bulk(endpoint_t *const ep,
" ep state: %d -> %d\n"
" usbsts: 0x%08"PRIx32"\n",
ret, ep_state,
- EC_GET(STATE, di->devctx.eps[ep_id]),
+ EC_GET(STATE, epctx),
xhci->opreg->usbsts);
return ret;
}
+ if (ep->direction == IN && data != src)
+ memcpy(src, data, ret);
return ret;
}
@@ -736,9 +769,9 @@ xhci_create_intr_queue(endpoint_t *const ep,
endpoint descriptor configured earlier. */
xhci_t *const xhci = XHCI_INST(ep->dev->controller);
+ const int slot_id = ep->dev->address;
const int ep_id = xhci_ep_id(ep);
- devinfo_t *const di = DEVINFO_FROM_XHCI(xhci, ep->dev->address);
- transfer_ring_t *const tr = di->transfer_rings[ep_id];
+ transfer_ring_t *const tr = xhci->dev[slot_id].transfer_rings[ep_id];
if (reqcount > (TRANSFER_RING_SIZE - 2)) {
xhci_debug("reqcount is too high, at most %d supported\n",
@@ -749,7 +782,7 @@ xhci_create_intr_queue(endpoint_t *const ep,
xhci_debug("reqsize is too large, at most 64KiB supported\n");
return NULL;
}
- if (di->interrupt_queues[ep_id]) {
+ if (xhci->dev[slot_id].interrupt_queues[ep_id]) {
xhci_debug("Only one interrupt queue per endpoint supported\n");
return NULL;
}
@@ -791,13 +824,13 @@ xhci_create_intr_queue(endpoint_t *const ep,
intrq->next = tr->cur;
intrq->ready = NULL;
intrq->ep = ep;
- di->interrupt_queues[ep_id] = intrq;
+ xhci->dev[slot_id].interrupt_queues[ep_id] = intrq;
/* Now enqueue all the prepared TRBs but the last
and ring the doorbell. */
for (i = 0; i < (reqcount - 1); ++i)
xhci_enqueue_trb(tr);
- xhci->dbreg[ep->dev->address] = ep_id;
+ xhci->dbreg[slot_id] = ep_id;
return intrq;
@@ -816,16 +849,15 @@ static void
xhci_destroy_intr_queue(endpoint_t *const ep, void *const q)
{
xhci_t *const xhci = XHCI_INST(ep->dev->controller);
+ const int slot_id = ep->dev->address;
const int ep_id = xhci_ep_id(ep);
- devinfo_t *const di = DEVINFO_FROM_XHCI(xhci, ep->dev->address);
- transfer_ring_t *const tr = di->transfer_rings[ep_id];
+ transfer_ring_t *const tr = xhci->dev[slot_id].transfer_rings[ep_id];
intrq_t *const intrq = (intrq_t *)q;
/* Make sure the endpoint is stopped */
- if (EC_GET(STATE, di->devctx.eps[ep_id]) == 1) {
- const int cc = xhci_cmd_stop_endpoint(
- xhci, ep->dev->address, ep_id);
+ if (EC_GET(STATE, xhci->dev[slot_id].ctx.ep[ep_id]) == 1) {
+ const int cc = xhci_cmd_stop_endpoint(xhci, slot_id, ep_id);
if (cc != CC_SUCCESS)
xhci_debug("Warning: Failed to stop endpoint\n");
}
@@ -839,11 +871,11 @@ xhci_destroy_intr_queue(endpoint_t *const ep, void *const q)
free(phys_to_virt(intrq->next->ptr_low));
intrq->next = xhci_next_trb(intrq->next, NULL);
}
- di->interrupt_queues[ep_id] = NULL;
+ xhci->dev[slot_id].interrupt_queues[ep_id] = NULL;
free((void *)intrq);
/* Reset the controller's dequeue pointer and reinitialize the ring */
- xhci_cmd_set_tr_dq(xhci, ep->dev->address, ep_id, tr->ring, 1);
+ xhci_cmd_set_tr_dq(xhci, slot_id, ep_id, tr->ring, 1);
xhci_init_cycle_ring(tr, TRANSFER_RING_SIZE);
}
@@ -868,8 +900,8 @@ xhci_poll_intr_queue(void *const q)
u8 *reqdata = NULL;
while (!reqdata && intrq->ready) {
const int ep_id = xhci_ep_id(ep);
- devinfo_t *const di = DEVINFO_FROM_XHCI(xhci, ep->dev->address);
- transfer_ring_t *const tr = di->transfer_rings[ep_id];
+ transfer_ring_t *const tr =
+ xhci->dev[ep->dev->address].transfer_rings[ep_id];
/* Fetch the request's buffer */
reqdata = phys_to_virt(intrq->next->ptr_low);