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