summaryrefslogtreecommitdiffstats
path: root/src/drivers/usb
diff options
context:
space:
mode:
authorMichael Brown2015-02-18 12:10:55 +0100
committerMichael Brown2015-02-18 12:10:55 +0100
commit645458e5a05a76b15cbef743fe1d3ff4ffa82569 (patch)
tree32349281082200015e520be9e7baf4852bd2fee7 /src/drivers/usb
parent[xhci] Leak memory if controller fails to disable slot (diff)
downloadipxe-645458e5a05a76b15cbef743fe1d3ff4ffa82569.tar.gz
ipxe-645458e5a05a76b15cbef743fe1d3ff4ffa82569.tar.xz
ipxe-645458e5a05a76b15cbef743fe1d3ff4ffa82569.zip
[xhci] Abort commands on timeout
When a command times out, abort it (via the Command Abort bit in the Command Ring Control Register) so that subsequent commands may execute as expected. This improves robustness when a device fails to respond to the Set Address command, since the subsequent Disable Slot command will now succeed. Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/drivers/usb')
-rw-r--r--src/drivers/usb/xhci.c82
-rw-r--r--src/drivers/usb/xhci.h15
2 files changed, 82 insertions, 15 deletions
diff --git a/src/drivers/usb/xhci.c b/src/drivers/usb/xhci.c
index 3574192d..a5bcb62b 100644
--- a/src/drivers/usb/xhci.c
+++ b/src/drivers/usb/xhci.c
@@ -1199,6 +1199,22 @@ static int xhci_ring_alloc ( struct xhci_device *xhci,
}
/**
+ * Reset transfer request block ring
+ *
+ * @v ring TRB ring
+ */
+static void xhci_ring_reset ( struct xhci_trb_ring *ring ) {
+ unsigned int count = ( 1U << ring->shift );
+
+ /* Reset producer and consumer counters */
+ ring->prod = 0;
+ ring->cons = 0;
+
+ /* Reset TRBs (except Link TRB) */
+ memset ( ring->trb, 0, ( count * sizeof ( ring->trb[0] ) ) );
+}
+
+/**
* Free transfer request block ring
*
* @v ring TRB ring
@@ -1574,6 +1590,22 @@ static void xhci_transfer ( struct xhci_device *xhci,
*/
static void xhci_complete ( struct xhci_device *xhci,
struct xhci_trb_complete *complete ) {
+ int rc;
+
+ /* Ignore "command ring stopped" notifications */
+ if ( complete->code == XHCI_CMPLT_CMD_STOPPED ) {
+ DBGC2 ( xhci, "XHCI %p command ring stopped\n", xhci );
+ return;
+ }
+
+ /* Ignore unexpected completions */
+ if ( ! xhci->pending ) {
+ rc = -ECODE ( complete->code );
+ DBGC ( xhci, "XHCI %p unexpected completion (code %d): %s\n",
+ xhci, complete->code, strerror ( rc ) );
+ DBGC_HDA ( xhci, 0, complete, sizeof ( *complete ) );
+ return;
+ }
/* Dequeue command TRB */
xhci_dequeue ( &xhci->command );
@@ -1582,15 +1614,9 @@ static void xhci_complete ( struct xhci_device *xhci,
assert ( xhci_ring_consumed ( &xhci->command ) ==
le64_to_cpu ( complete->command ) );
- /* Record completion if applicable */
- if ( xhci->completion ) {
- memcpy ( xhci->completion, complete,
- sizeof ( *xhci->completion ) );
- xhci->completion = NULL;
- } else {
- DBGC ( xhci, "XHCI %p unexpected completion:\n", xhci );
- DBGC_HDA ( xhci, 0, complete, sizeof ( *complete ) );
- }
+ /* Record completion */
+ memcpy ( xhci->pending, complete, sizeof ( *xhci->pending ) );
+ xhci->pending = NULL;
}
/**
@@ -1697,6 +1723,33 @@ static void xhci_event_poll ( struct xhci_device *xhci ) {
}
/**
+ * Abort command
+ *
+ * @v xhci xHCI device
+ */
+static void xhci_abort ( struct xhci_device *xhci ) {
+ physaddr_t crp;
+
+ /* Abort the command */
+ DBGC2 ( xhci, "XHCI %p aborting command\n", xhci );
+ xhci_writeq ( xhci, XHCI_CRCR_CA, xhci->op + XHCI_OP_CRCR );
+
+ /* Allow time for command to abort */
+ mdelay ( XHCI_COMMAND_ABORT_DELAY_MS );
+
+ /* Sanity check */
+ assert ( ( readl ( xhci->op + XHCI_OP_CRCR ) & XHCI_CRCR_CRR ) == 0 );
+
+ /* Consume (and ignore) any final command status */
+ xhci_event_poll ( xhci );
+
+ /* Reset the command ring control register */
+ xhci_ring_reset ( &xhci->command );
+ crp = virt_to_phys ( xhci->command.trb );
+ xhci_writeq ( xhci, ( crp | XHCI_CRCR_RCS ), xhci->op + XHCI_OP_CRCR );
+}
+
+/**
* Issue command and wait for completion
*
* @v xhci xHCI device
@@ -1711,8 +1764,8 @@ static int xhci_command ( struct xhci_device *xhci, union xhci_trb *trb ) {
unsigned int i;
int rc;
- /* Record the completion buffer */
- xhci->completion = trb;
+ /* Record the pending command */
+ xhci->pending = trb;
/* Enqueue the command */
if ( ( rc = xhci_enqueue ( &xhci->command, NULL, trb ) ) != 0 )
@@ -1728,7 +1781,7 @@ static int xhci_command ( struct xhci_device *xhci, union xhci_trb *trb ) {
xhci_event_poll ( xhci );
/* Check for completion */
- if ( ! xhci->completion ) {
+ if ( ! xhci->pending ) {
if ( complete->code != XHCI_CMPLT_SUCCESS ) {
rc = -ECODE ( complete->code );
DBGC ( xhci, "XHCI %p command failed (code "
@@ -1748,8 +1801,11 @@ static int xhci_command ( struct xhci_device *xhci, union xhci_trb *trb ) {
DBGC ( xhci, "XHCI %p timed out waiting for completion\n", xhci );
rc = -ETIMEDOUT;
+ /* Abort command */
+ xhci_abort ( xhci );
+
err_enqueue:
- xhci->completion = NULL;
+ xhci->pending = NULL;
return rc;
}
diff --git a/src/drivers/usb/xhci.h b/src/drivers/usb/xhci.h
index ec951bd6..30c6d1f4 100644
--- a/src/drivers/usb/xhci.h
+++ b/src/drivers/usb/xhci.h
@@ -178,6 +178,9 @@ enum xhci_default_psi_value {
/** Command ring cycle state */
#define XHCI_CRCR_RCS 0x00000001UL
+/** Command abort */
+#define XHCI_CRCR_CA 0x00000004UL
+
/** Command ring running */
#define XHCI_CRCR_CRR 0x00000008UL
@@ -629,6 +632,8 @@ enum xhci_completion_code {
XHCI_CMPLT_SUCCESS = 1,
/** Short packet */
XHCI_CMPLT_SHORT = 13,
+ /** Command ring stopped */
+ XHCI_CMPLT_CMD_STOPPED = 24,
};
/** A port status change transfer request block */
@@ -987,6 +992,12 @@ xhci_ring_consumed ( struct xhci_trb_ring *ring ) {
*/
#define XHCI_COMMAND_MAX_WAIT_MS 500
+/** Time to delay after aborting a command
+ *
+ * This is a policy decision
+ */
+#define XHCI_COMMAND_ABORT_DELAY_MS 500
+
/** Maximum time to wait for a port reset to complete
*
* This is a policy decision.
@@ -1042,8 +1053,8 @@ struct xhci_device {
struct xhci_trb_ring command;
/** Event ring */
struct xhci_event_ring event;
- /** Current command completion buffer (if any) */
- union xhci_trb *completion;
+ /** Current command (if any) */
+ union xhci_trb *pending;
/** Device slots, indexed by slot ID */
struct xhci_slot **slot;