diff options
Diffstat (limited to 'src/drivers/usb/xhci.c')
-rw-r--r-- | src/drivers/usb/xhci.c | 51 |
1 files changed, 49 insertions, 2 deletions
diff --git a/src/drivers/usb/xhci.c b/src/drivers/usb/xhci.c index cc48af033..3d98b1e10 100644 --- a/src/drivers/usb/xhci.c +++ b/src/drivers/usb/xhci.c @@ -1165,6 +1165,31 @@ static int xhci_reset ( struct xhci_device *xhci ) { return -ETIMEDOUT; } +/** + * Mark xHCI device as permanently failed + * + * @v xhci xHCI device + * @ret rc Return status code + */ +static int xhci_fail ( struct xhci_device *xhci ) { + size_t len; + int rc; + + /* Mark command mechanism as permanently failed */ + xhci->failed = 1; + + /* Reset device */ + if ( ( rc = xhci_reset ( xhci ) ) != 0 ) + return rc; + + /* Discard DCBAA entries since DCBAAP has been cleared */ + assert ( xhci->dcbaa.context != NULL ); + len = ( ( xhci->slots + 1 ) * sizeof ( xhci->dcbaa.context[0] ) ); + memset ( xhci->dcbaa.context, 0, len ); + + return 0; +} + /****************************************************************************** * * Transfer request blocks @@ -1720,6 +1745,10 @@ static void xhci_event_poll ( struct xhci_device *xhci ) { unsigned int consumed; unsigned int type; + /* Do nothing if device has permanently failed */ + if ( xhci->failed ) + return; + /* Poll for events */ profile_start ( &xhci_event_profiler ); for ( consumed = 0 ; ; consumed++ ) { @@ -1778,6 +1807,7 @@ static void xhci_event_poll ( struct xhci_device *xhci ) { */ static void xhci_abort ( struct xhci_device *xhci ) { physaddr_t crp; + uint32_t crcr; /* Abort the command */ DBGC2 ( xhci, "XHCI %s aborting command\n", xhci->name ); @@ -1786,8 +1816,18 @@ static void xhci_abort ( struct xhci_device *xhci ) { /* 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 ); + /* Check for failure to abort */ + crcr = readl ( xhci->op + XHCI_OP_CRCR ); + if ( crcr & XHCI_CRCR_CRR ) { + + /* Device has failed to abort a command and is almost + * certainly beyond repair. Reset device, abandoning + * all state, and mark device as failed to avoid + * delays on any future command attempts. + */ + DBGC ( xhci, "XHCI %s failed to abort command\n", xhci->name ); + xhci_fail ( xhci ); + } /* Consume (and ignore) any final command status */ xhci_event_poll ( xhci ); @@ -1813,6 +1853,12 @@ static int xhci_command ( struct xhci_device *xhci, union xhci_trb *trb ) { unsigned int i; int rc; + /* Immediately fail all commands if command mechanism has failed */ + if ( xhci->failed ) { + rc = -EPIPE; + goto err_failed; + } + /* Sanity check */ if ( xhci->pending ) { DBGC ( xhci, "XHCI %s command ring busy\n", xhci->name ); @@ -1863,6 +1909,7 @@ static int xhci_command ( struct xhci_device *xhci, union xhci_trb *trb ) { err_enqueue: xhci->pending = NULL; err_pending: + err_failed: return rc; } |