summaryrefslogtreecommitdiff
path: root/payloads/libpayload/drivers/usb/xhci_events.c
diff options
context:
space:
mode:
Diffstat (limited to 'payloads/libpayload/drivers/usb/xhci_events.c')
-rw-r--r--payloads/libpayload/drivers/usb/xhci_events.c333
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;
+}