328 lines
9.2 KiB
C
328 lines
9.2 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 <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);
|
||
|
|
||
|
intrq_t *intrq;
|
||
|
|
||
|
if (id && id <= xhci->max_slots_en &&
|
||
|
(intrq = xhci->dev[id].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 = USB_MAX_PROCESSING_TIME_US; /* 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)
|
||
|
{
|
||
|
unsigned long timeout_us = USB_MAX_PROCESSING_TIME_US; /* 5s */
|
||
|
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 amount of bytes transferred on success, negative CC on error */
|
||
|
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);
|
||
|
/* 5s for all types of transfers */
|
||
|
unsigned long timeout_us = USB_MAX_PROCESSING_TIME_US;
|
||
|
int ret = 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) {
|
||
|
ret = -TRB_GET(CC, xhci->er.cur);
|
||
|
if (ret == -CC_SUCCESS || ret == -CC_SHORT_PACKET)
|
||
|
ret = TRB_GET(EVTL, 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 ret;
|
||
|
}
|