diff options
Diffstat (limited to 'payloads/libpayload/drivers/usb/xhci_events.c')
-rw-r--r-- | payloads/libpayload/drivers/usb/xhci_events.c | 333 |
1 files changed, 333 insertions, 0 deletions
diff --git a/payloads/libpayload/drivers/usb/xhci_events.c b/payloads/libpayload/drivers/usb/xhci_events.c new file mode 100644 index 0000000000..b04ecda267 --- /dev/null +++ b/payloads/libpayload/drivers/usb/xhci_events.c @@ -0,0 +1,333 @@ +/* + * 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 <inttypes.h> +#include <arch/virtual.h> +#include "xhci_private.h" + +void +xhci_reset_event_ring(event_ring_t *const er) +{ + int i; + for (i = 0; i < EVENT_RING_SIZE; ++i) + er->ring[i].control &= ~TRB_CYCLE; + er->cur = er->ring; + er->last = er->ring + EVENT_RING_SIZE; + er->ccs = 1; + er->adv = 1; +} + +static inline int +xhci_event_ready(const event_ring_t *const er) +{ + return (er->cur->control & TRB_CYCLE) == er->ccs; +} + +void +xhci_update_event_dq(xhci_t *const xhci) +{ + if (xhci->er.adv) { + xhci_spew("Updating dq ptr: @%p(0x%08"PRIx32") -> %p\n", + phys_to_virt(xhci->hcrreg->intrrs[0].erdp_lo), + xhci->hcrreg->intrrs[0].erdp_lo, xhci->er.cur); + xhci->hcrreg->intrrs[0].erdp_lo = virt_to_phys(xhci->er.cur); + xhci->hcrreg->intrrs[0].erdp_hi = 0; + xhci->er.adv = 0; + } +} + +void +xhci_advance_event_ring(xhci_t *const xhci) +{ + xhci->er.cur++; + xhci->er.adv = 1; + if (xhci->er.cur == xhci->er.last) { + xhci_spew("Roll over in event ring\n"); + xhci->er.cur = xhci->er.ring; + xhci->er.ccs ^= 1; + xhci_update_event_dq(xhci); + } +} + +static void +xhci_handle_transfer_event(xhci_t *const xhci) +{ + const trb_t *const ev = xhci->er.cur; + + const int cc = TRB_GET(CC, ev); + const int id = TRB_GET(ID, ev); + const int ep = TRB_GET(EP, ev); + + devinfo_t *di; + intrq_t *intrq; + + if (id && id <= xhci->max_slots_en && + (di = DEVINFO_FROM_XHCI(xhci, id)) && + (intrq = di->interrupt_queues[ep])) { + /* It's a running interrupt endpoint */ + intrq->ready = phys_to_virt(ev->ptr_low); + if (cc == CC_SUCCESS || cc == CC_SHORT_PACKET) { + TRB_SET(TL, intrq->ready, + intrq->size - TRB_GET(EVTL, ev)); + } else { + xhci_debug("Interrupt Transfer failed: %d\n", + cc); + TRB_SET(TL, intrq->ready, 0); + } + } else if (cc == CC_STOPPED || cc == CC_STOPPED_LENGTH_INVALID) { + /* Ignore 'Forced Stop Events' */ + } else { + xhci_debug("Warning: " + "Spurious transfer event for ID %d, EP %d:\n" + " Pointer: 0x%08x%08x\n" + " TL: 0x%06x\n" + " CC: %d\n", + id, ep, + ev->ptr_high, ev->ptr_low, + TRB_GET(EVTL, ev), cc); + } + xhci_advance_event_ring(xhci); +} + +static void +xhci_handle_command_completion_event(xhci_t *const xhci) +{ + const trb_t *const ev = xhci->er.cur; + + xhci_debug("Warning: Spurious command completion event:\n" + " Pointer: 0x%08x%08x\n" + " CC: %d\n" + " Slot ID: %d\n" + " Cycle: %d\n", + ev->ptr_high, ev->ptr_low, + TRB_GET(CC, ev), TRB_GET(ID, ev), ev->control & TRB_CYCLE); + xhci_advance_event_ring(xhci); +} + +static void +xhci_handle_host_controller_event(xhci_t *const xhci) +{ + const trb_t *const ev = xhci->er.cur; + + const int cc = TRB_GET(CC, ev); + switch (cc) { + case CC_EVENT_RING_FULL_ERROR: + xhci_debug("Event ring full! (@%p)\n", xhci->er.cur); + /* + * If we get here, we have processed the whole queue: + * xHC pushes this event, when it sees the ring full, + * full of other events. + * IMO it's save and necessary to update the dequeue + * pointer here. + */ + xhci_advance_event_ring(xhci); + xhci_update_event_dq(xhci); + break; + default: + xhci_debug("Warning: Spurious host controller event: %d\n", cc); + xhci_advance_event_ring(xhci); + break; + } +} + +/* handle standard types: + * - command completion event + * - port status change event + * - transfer event + * - host controller event + */ +static void +xhci_handle_event(xhci_t *const xhci) +{ + const trb_t *const ev = xhci->er.cur; + + const int trb_type = TRB_GET(TT, ev); + switch (trb_type) { + /* Either pass along the event or advance event ring */ + case TRB_EV_TRANSFER: + xhci_handle_transfer_event(xhci); + break; + case TRB_EV_CMD_CMPL: + xhci_handle_command_completion_event(xhci); + break; + case TRB_EV_PORTSC: + xhci_debug("Port Status Change Event for %d: %d\n", + TRB_GET(PORT, ev), TRB_GET(CC, ev)); + /* We ignore the event as we look for the PORTSC + registers instead, at a time when it suits _us_. */ + xhci_advance_event_ring(xhci); + break; + case TRB_EV_HOST: + xhci_handle_host_controller_event(xhci); + break; + default: + xhci_debug("Warning: Spurious event: %d, Completion Code: %d\n", + trb_type, TRB_GET(CC, ev)); + xhci_advance_event_ring(xhci); + break; + } +} + +void +xhci_handle_events(xhci_t *const xhci) +{ + while (xhci_event_ready(&xhci->er)) + xhci_handle_event(xhci); + xhci_update_event_dq(xhci); +} + +static unsigned long +xhci_wait_for_event(const event_ring_t *const er, + unsigned long *const timeout_us) +{ + while (!xhci_event_ready(er) && *timeout_us) { + --*timeout_us; + udelay(1); + } + return *timeout_us; +} + +static unsigned long +xhci_wait_for_event_type(xhci_t *const xhci, + const int trb_type, + unsigned long *const timeout_us) +{ + while (xhci_wait_for_event(&xhci->er, timeout_us)) { + if (TRB_GET(TT, xhci->er.cur) == trb_type) + break; + + xhci_handle_event(xhci); + } + return *timeout_us; +} + +/* returns cc of command in question (pointed to by `address`) */ +int +xhci_wait_for_command_aborted(xhci_t *const xhci, const trb_t *const address) +{ + /* + * Specification says that something might be seriously wrong, if + * we don't get a response after 5s. Still, let the caller decide, + * what to do then. + */ + unsigned long timeout_us = 5 * 1000 * 1000; /* 5s */ + int cc = TIMEOUT; + /* + * Expects two command completion events: + * The first with CC == COMMAND_ABORTED should point to address, + * the second with CC == COMMAND_RING_STOPPED should point to new dq. + */ + while (xhci_wait_for_event_type(xhci, TRB_EV_CMD_CMPL, &timeout_us)) { + if ((xhci->er.cur->ptr_low == virt_to_phys(address)) && + (xhci->er.cur->ptr_high == 0)) { + cc = TRB_GET(CC, xhci->er.cur); + xhci_advance_event_ring(xhci); + break; + } + + xhci_handle_command_completion_event(xhci); + } + if (!timeout_us) + xhci_debug("Warning: Timed out waiting for COMMAND_ABORTED.\n"); + while (xhci_wait_for_event_type(xhci, TRB_EV_CMD_CMPL, &timeout_us)) { + if (TRB_GET(CC, xhci->er.cur) == CC_COMMAND_RING_STOPPED) { + xhci->cr.cur = phys_to_virt(xhci->er.cur->ptr_low); + xhci_advance_event_ring(xhci); + break; + } + + xhci_handle_command_completion_event(xhci); + } + if (!timeout_us) + xhci_debug("Warning: Timed out " + "waiting for COMMAND_RING_STOPPED.\n"); + xhci_update_event_dq(xhci); + return cc; +} + +/* + * returns cc of command in question (pointed to by `address`) + * caller should abort command if cc is TIMEOUT + */ +int +xhci_wait_for_command_done(xhci_t *const xhci, + const trb_t *const address, + const int clear_event) +{ + /* + * The Address Device Command should take most time, as it has to + * communicate with the USB device. Set address processing shouldn't + * take longer than 50ms (at the slave). Let's take a timeout of + * 100ms. + */ + unsigned long timeout_us = 100 * 1000; /* 100ms */ + int cc = TIMEOUT; + while (xhci_wait_for_event_type(xhci, TRB_EV_CMD_CMPL, &timeout_us)) { + if ((xhci->er.cur->ptr_low == virt_to_phys(address)) && + (xhci->er.cur->ptr_high == 0)) { + cc = TRB_GET(CC, xhci->er.cur); + break; + } + + xhci_handle_command_completion_event(xhci); + } + if (!timeout_us) { + xhci_debug("Warning: Timed out waiting for TRB_EV_CMD_CMPL.\n"); + } else if (clear_event) { + xhci_advance_event_ring(xhci); + } + xhci_update_event_dq(xhci); + return cc; +} + +/* returns cc of transfer for given slot/endpoint pair */ +int +xhci_wait_for_transfer(xhci_t *const xhci, const int slot_id, const int ep_id) +{ + xhci_spew("Waiting for transfer on ID %d EP %d\n", slot_id, ep_id); + /* 2s for all types of transfers */ /* TODO: test, wait longer? */ + unsigned long timeout_us = 2 * 1000 * 1000; + int cc = TIMEOUT; + while (xhci_wait_for_event_type(xhci, TRB_EV_TRANSFER, &timeout_us)) { + if (TRB_GET(ID, xhci->er.cur) == slot_id && + TRB_GET(EP, xhci->er.cur) == ep_id) { + cc = TRB_GET(CC, xhci->er.cur); + xhci_advance_event_ring(xhci); + break; + } + + xhci_handle_transfer_event(xhci); + } + if (!timeout_us) + xhci_debug("Warning: Timed out waiting for TRB_EV_TRANSFER.\n"); + xhci_update_event_dq(xhci); + return cc; +} |