MdeModulePkg/XhciDxe: Check timeout URB again after stopping endpoint

This fixes BULK data loss when transfer is detected as timeout but
finished just before stopping endpoint.

Contributed-under: TianoCore Contribution Agreement 1.0
Signed-off-by: Ruiyu Ni <ruiyu.ni@intel.com>
Reviewed-by: Hao Wu <hao.a.wu@intel.com>
Cc: Star Zeng <star.zeng@intel.com>
Cc: Feng Tian <feng.tian@intel.com>
This commit is contained in:
Ruiyu Ni 2017-07-03 17:53:49 +08:00
parent 41fb8ce939
commit 49be9c3c20
4 changed files with 90 additions and 29 deletions

View File

@ -780,26 +780,32 @@ XhcTransfer (
Status = XhcExecTransfer (Xhc, FALSE, Urb, Timeout);
*TransferResult = Urb->Result;
*DataLength = Urb->Completed;
if (Status == EFI_TIMEOUT) {
//
// The transfer timed out. Abort the transfer by dequeueing of the TD.
//
RecoveryStatus = XhcDequeueTrbFromEndpoint (Xhc, Urb);
if (EFI_ERROR (RecoveryStatus)) {
DEBUG((DEBUG_ERROR, "XhcTransfer[Type=%d]: XhcDequeueTrbFromEndpoint failed\n", Type));
}
} else {
if (*TransferResult == EFI_USB_NOERROR) {
RecoveryStatus = XhcDequeueTrbFromEndpoint(Xhc, Urb);
if (RecoveryStatus == EFI_ALREADY_STARTED) {
//
// The URB is finished just before stopping endpoint.
// Change returning status from EFI_TIMEOUT to EFI_SUCCESS.
//
ASSERT (Urb->Result == EFI_USB_NOERROR);
Status = EFI_SUCCESS;
} else if (*TransferResult == EFI_USB_ERR_STALL) {
RecoveryStatus = XhcRecoverHaltedEndpoint (Xhc, Urb);
if (EFI_ERROR (RecoveryStatus)) {
DEBUG ((DEBUG_ERROR, "XhcTransfer[Type=%d]: XhcRecoverHaltedEndpoint failed\n", Type));
DEBUG ((DEBUG_ERROR, "XhcTransfer[Type=%d]: pending URB is finished, Length = %d.\n", Type, Urb->Completed));
} else if (EFI_ERROR(RecoveryStatus)) {
DEBUG((DEBUG_ERROR, "XhcTransfer[Type=%d]: XhcDequeueTrbFromEndpoint failed!\n", Type));
}
Status = EFI_DEVICE_ERROR;
}
*TransferResult = Urb->Result;
*DataLength = Urb->Completed;
if (*TransferResult == EFI_USB_ERR_STALL) {
ASSERT (Status == EFI_DEVICE_ERROR);
RecoveryStatus = XhcRecoverHaltedEndpoint(Xhc, Urb);
if (EFI_ERROR (RecoveryStatus)) {
DEBUG ((DEBUG_ERROR, "XhcTransfer[Type=%d]: XhcRecoverHaltedEndpoint failed!\n", Type));
}
}

View File

@ -2,7 +2,7 @@
Provides some data structure definitions used by the XHCI host controller driver.
Copyright (c) 2011 - 2016, Intel Corporation. All rights reserved.<BR>
Copyright (c) 2011 - 2017, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
@ -243,6 +243,7 @@ struct _USB_XHCI_INSTANCE {
UINT64 *DCBAA;
VOID *DCBAAMap;
UINT32 MaxSlotsEn;
URB *PendingUrb;
//
// Cmd Transfer Ring
//

View File

@ -696,6 +696,7 @@ Done:
@param Urb The urb which doesn't get completed in a specified timeout range.
@retval EFI_SUCCESS The dequeuing of the TDs is successful.
@retval EFI_ALREADY_STARTED The Urb is finished so no deque is needed.
@retval Others Failed to stop the endpoint and dequeue the TDs.
**/
@ -723,7 +724,7 @@ XhcDequeueTrbFromEndpoint (
//
// 1) Send Stop endpoint command to stop xHC from executing of the TDs on the endpoint
//
Status = XhcStopEndpoint(Xhc, SlotId, Dci);
Status = XhcStopEndpoint(Xhc, SlotId, Dci, Urb);
if (EFI_ERROR(Status)) {
DEBUG ((EFI_D_ERROR, "XhcDequeueTrbFromEndpoint: Stop Endpoint Failed, Status = %r\n", Status));
goto Done;
@ -732,11 +733,21 @@ XhcDequeueTrbFromEndpoint (
//
// 2)Set dequeue pointer
//
if (Urb->Finished && Urb->Result == EFI_USB_NOERROR) {
//
// Return Already Started to indicate the pending URB is finished.
// This fixes BULK data loss when transfer is detected as timeout
// but finished just before stopping endpoint.
//
Status = EFI_ALREADY_STARTED;
DEBUG ((DEBUG_INFO, "XhcDequeueTrbFromEndpoint: Pending URB is finished: Length Actual/Expect = %d/%d!\n", Urb->Completed, Urb->DataLen));
} else {
Status = XhcSetTrDequeuePointer(Xhc, SlotId, Dci, Urb);
if (EFI_ERROR(Status)) {
DEBUG ((EFI_D_ERROR, "XhcDequeueTrbFromEndpoint: Set Transfer Ring Dequeue Pointer Failed, Status = %r\n", Status));
if (EFI_ERROR (Status)) {
DEBUG ((DEBUG_ERROR, "XhcDequeueTrbFromEndpoint: Set Transfer Ring Dequeue Pointer Failed, Status = %r\n", Status));
goto Done;
}
}
//
// 3)Ring the doorbell to transit from stop to active
@ -1125,12 +1136,14 @@ XhcCheckUrbResult (
TRBPtr = (TRB_TEMPLATE *)(UINTN) UsbHcGetHostAddrForPciAddr (Xhc->MemPool, (VOID *)(UINTN) PhyAddr, sizeof (TRB_TEMPLATE));
//
// Update the status of Urb according to the finished event regardless of whether
// the urb is current checked one or in the XHCI's async transfer list.
// Update the status of URB including the pending URB, the URB that is currently checked,
// and URBs in the XHCI's async interrupt transfer list.
// This way is used to avoid that those completed async transfer events don't get
// handled in time and are flushed by newer coming events.
//
if (IsTransferRingTrb (Xhc, TRBPtr, Urb)) {
if (Xhc->PendingUrb != NULL && IsTransferRingTrb (Xhc, TRBPtr, Xhc->PendingUrb)) {
CheckedUrb = Xhc->PendingUrb;
} else if (IsTransferRingTrb (Xhc, TRBPtr, Urb)) {
CheckedUrb = Urb;
} else if (IsAsyncIntTrb (Xhc, TRBPtr, &AsyncUrb)) {
CheckedUrb = AsyncUrb;
@ -1163,6 +1176,16 @@ XhcCheckUrbResult (
DEBUG ((EFI_D_ERROR, "XhcCheckUrbResult: TRANSACTION_ERROR! Completecode = %x\n",EvtTrb->Completecode));
goto EXIT;
case TRB_COMPLETION_STOPPED:
case TRB_COMPLETION_STOPPED_LENGTH_INVALID:
CheckedUrb->Result |= EFI_USB_ERR_TIMEOUT;
CheckedUrb->Finished = TRUE;
//
// The pending URB is timeout and force stopped when stopping endpoint.
// Continue the loop to receive the Command Complete Event for stopping endpoint.
//
continue;
case TRB_COMPLETION_SHORT_PACKET:
case TRB_COMPLETION_SUCCESS:
if (EvtTrb->Completecode == TRB_COMPLETION_SHORT_PACKET) {
@ -3155,6 +3178,7 @@ XhcSetConfigCmd64 (
@param Xhc The XHCI Instance.
@param SlotId The slot id to be configured.
@param Dci The device context index of endpoint.
@param PendingUrb The pending URB to check completion status when stopping the end point.
@retval EFI_SUCCESS Stop endpoint successfully.
@retval Others Failed to stop endpoint.
@ -3165,7 +3189,8 @@ EFIAPI
XhcStopEndpoint (
IN USB_XHCI_INSTANCE *Xhc,
IN UINT8 SlotId,
IN UINT8 Dci
IN UINT8 Dci,
IN URB *PendingUrb OPTIONAL
)
{
EFI_STATUS Status;
@ -3174,6 +3199,29 @@ XhcStopEndpoint (
DEBUG ((EFI_D_INFO, "XhcStopEndpoint: Slot = 0x%x, Dci = 0x%x\n", SlotId, Dci));
//
// When XhcCheckUrbResult waits for the Stop_Endpoint completion, it also checks
// the PendingUrb completion status, because it's possible that the PendingUrb is
// finished just before stopping the end point, but after the looping check.
//
// The PendingUrb could be passed to XhcCmdTransfer to XhcExecTransfer to XhcCheckUrbResult
// through function parameter, but That will cause every consumer of XhcCmdTransfer,
// XhcExecTransfer and XhcCheckUrbResult pass a NULL PendingUrb.
// But actually only XhcCheckUrbResult is aware of the PendingUrb.
// So we choose to save the PendingUrb into the USB_XHCI_INSTANCE and use it in XhcCheckUrbResult.
//
ASSERT (Xhc->PendingUrb == NULL);
Xhc->PendingUrb = PendingUrb;
//
// Reset the URB result from Timeout to NoError.
// The USB result will be:
// changed to Timeout when Stop/StopInvalidLength Transfer Event is received, or
// remain NoError when Success/ShortPacket Transfer Event is received.
//
if (PendingUrb != NULL) {
PendingUrb->Result = EFI_USB_NOERROR;
}
//
// Send stop endpoint command to transit Endpoint from running to stop state
//
@ -3192,6 +3240,8 @@ XhcStopEndpoint (
DEBUG ((EFI_D_ERROR, "XhcStopEndpoint: Stop Endpoint Failed, Status = %r\n", Status));
}
Xhc->PendingUrb = NULL;
return Status;
}
@ -3418,7 +3468,7 @@ XhcSetInterface (
// XHCI 4.3.6 - Setting Alternate Interfaces
// 1) Stop any Running Transfer Rings affected by the Alternate Interface setting.
//
Status = XhcStopEndpoint (Xhc, SlotId, Dci);
Status = XhcStopEndpoint (Xhc, SlotId, Dci, NULL);
if (EFI_ERROR (Status)) {
return Status;
}
@ -3620,7 +3670,7 @@ XhcSetInterface64 (
// XHCI 4.3.6 - Setting Alternate Interfaces
// 1) Stop any Running Transfer Rings affected by the Alternate Interface setting.
//
Status = XhcStopEndpoint (Xhc, SlotId, Dci);
Status = XhcStopEndpoint (Xhc, SlotId, Dci, NULL);
if (EFI_ERROR (Status)) {
return Status;
}

View File

@ -2,7 +2,7 @@
This file contains the definition for XHCI host controller schedule routines.
Copyright (c) 2011 - 2015, Intel Corporation. All rights reserved.<BR>
Copyright (c) 2011 - 2017, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution. The full text of the license may be found at
@ -80,6 +80,8 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
#define TRB_COMPLETION_TRB_ERROR 5
#define TRB_COMPLETION_STALL_ERROR 6
#define TRB_COMPLETION_SHORT_PACKET 13
#define TRB_COMPLETION_STOPPED 26
#define TRB_COMPLETION_STOPPED_LENGTH_INVALID 27
//
// The topology string used to present usb device location
@ -1343,6 +1345,7 @@ XhcDequeueTrbFromEndpoint (
@param Xhc The XHCI Instance.
@param SlotId The slot id to be configured.
@param Dci The device context index of endpoint.
@param PendingUrb The pending URB to check completion status when stopping the end point.
@retval EFI_SUCCESS Stop endpoint successfully.
@retval Others Failed to stop endpoint.
@ -1353,7 +1356,8 @@ EFIAPI
XhcStopEndpoint (
IN USB_XHCI_INSTANCE *Xhc,
IN UINT8 SlotId,
IN UINT8 Dci
IN UINT8 Dci,
IN URB *PendingUrb OPTIONAL
);
/**