diff options
author | Michael Brown <mcb30@ipxe.org> | 2015-02-18 11:10:55 +0000 |
---|---|---|
committer | Michael Brown <mcb30@ipxe.org> | 2015-02-18 11:10:55 +0000 |
commit | 645458e5a05a76b15cbef743fe1d3ff4ffa82569 (patch) | |
tree | 32349281082200015e520be9e7baf4852bd2fee7 | |
parent | 88448de720f1b0261a33355295248766e51303fe (diff) | |
download | ipxe-645458e5a05a76b15cbef743fe1d3ff4ffa82569.zip ipxe-645458e5a05a76b15cbef743fe1d3ff4ffa82569.tar.gz ipxe-645458e5a05a76b15cbef743fe1d3ff4ffa82569.tar.bz2 |
[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>
-rw-r--r-- | src/drivers/usb/xhci.c | 82 | ||||
-rw-r--r-- | src/drivers/usb/xhci.h | 15 |
2 files changed, 82 insertions, 15 deletions
diff --git a/src/drivers/usb/xhci.c b/src/drivers/usb/xhci.c index 3574192..a5bcb62 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 ec951bd..30c6d1f 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; |