/** @file | |
Debug Port Library implementation based on usb3 debug port. | |
Copyright (c) 2014 - 2015, 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 | |
http://opensource.org/licenses/bsd-license.php. | |
THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS, | |
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED. | |
**/ | |
#include "DebugCommunicationLibUsb3Internal.h" | |
/** | |
Synchronize the specified transfer ring to update the enqueue and dequeue pointer. | |
@param Handle Debug port handle. | |
@param TrsRing The transfer ring to sync. | |
@retval EFI_SUCCESS The transfer ring is synchronized successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
XhcSyncTrsRing ( | |
IN USB3_DEBUG_PORT_HANDLE *Handle, | |
IN TRANSFER_RING *TrsRing | |
) | |
{ | |
UINTN Index; | |
TRB_TEMPLATE *TrsTrb; | |
UINT32 CycleBit; | |
ASSERT (TrsRing != NULL); | |
// | |
// Calculate the latest RingEnqueue and RingPCS | |
// | |
TrsTrb = (TRB_TEMPLATE *)(UINTN) TrsRing->RingEnqueue; | |
ASSERT (TrsTrb != NULL); | |
for (Index = 0; Index < TrsRing->TrbNumber; Index++) { | |
if (TrsTrb->CycleBit != (TrsRing->RingPCS & BIT0)) { | |
break; | |
} | |
TrsTrb++; | |
if ((UINT8) TrsTrb->Type == TRB_TYPE_LINK) { | |
ASSERT (((LINK_TRB*)TrsTrb)->TC != 0); | |
// | |
// set cycle bit in Link TRB as normal | |
// | |
((LINK_TRB*)TrsTrb)->CycleBit = TrsRing->RingPCS & BIT0; | |
// | |
// Toggle PCS maintained by software | |
// | |
TrsRing->RingPCS = (TrsRing->RingPCS & BIT0) ? 0 : 1; | |
TrsTrb = (TRB_TEMPLATE *)(UINTN)((TrsTrb->Parameter1 | LShiftU64 ((UINT64)TrsTrb->Parameter2, 32)) & ~0x0F); | |
} | |
} | |
ASSERT (Index != TrsRing->TrbNumber); | |
if ((EFI_PHYSICAL_ADDRESS)(UINTN) TrsTrb != TrsRing->RingEnqueue) { | |
TrsRing->RingEnqueue = (EFI_PHYSICAL_ADDRESS)(UINTN) TrsTrb; | |
} | |
// | |
// Clear the Trb context for enqueue, but reserve the PCS bit which indicates free Trb. | |
// | |
CycleBit = TrsTrb->CycleBit; | |
ZeroMem (TrsTrb, sizeof (TRB_TEMPLATE)); | |
TrsTrb->CycleBit = CycleBit; | |
return EFI_SUCCESS; | |
} | |
/** | |
Synchronize the specified event ring to update the enqueue and dequeue pointer. | |
@param Handle Debug port handle. | |
@param EvtRing The event ring to sync. | |
@retval EFI_SUCCESS The event ring is synchronized successfully. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
XhcSyncEventRing ( | |
IN USB3_DEBUG_PORT_HANDLE *Handle, | |
IN EVENT_RING *EvtRing | |
) | |
{ | |
UINTN Index; | |
TRB_TEMPLATE *EvtTrb1; | |
ASSERT (EvtRing != NULL); | |
// | |
// Calculate the EventRingEnqueue and EventRingCCS. | |
// Note: only support single Segment | |
// | |
EvtTrb1 = (TRB_TEMPLATE *)(UINTN) EvtRing->EventRingDequeue; | |
for (Index = 0; Index < EvtRing->TrbNumber; Index++) { | |
if (EvtTrb1->CycleBit != EvtRing->EventRingCCS) { | |
break; | |
} | |
EvtTrb1++; | |
if ((UINTN)EvtTrb1 >= ((UINTN) EvtRing->EventRingSeg0 + sizeof (TRB_TEMPLATE) * EvtRing->TrbNumber)) { | |
EvtTrb1 = (TRB_TEMPLATE *)(UINTN) EvtRing->EventRingSeg0; | |
EvtRing->EventRingCCS = (EvtRing->EventRingCCS) ? 0 : 1; | |
} | |
} | |
if (Index < EvtRing->TrbNumber) { | |
EvtRing->EventRingEnqueue = (EFI_PHYSICAL_ADDRESS)(UINTN)EvtTrb1; | |
} else { | |
ASSERT (FALSE); | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Check if there is a new generated event. | |
@param Handle Debug port handle. | |
@param EvtRing The event ring to check. | |
@param NewEvtTrb The new event TRB found. | |
@retval EFI_SUCCESS Found a new event TRB at the event ring. | |
@retval EFI_NOT_READY The event ring has no new event. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
XhcCheckNewEvent ( | |
IN USB3_DEBUG_PORT_HANDLE *Handle, | |
IN EVENT_RING *EvtRing, | |
OUT TRB_TEMPLATE **NewEvtTrb | |
) | |
{ | |
EFI_STATUS Status; | |
TRB_TEMPLATE *EvtTrb; | |
ASSERT (EvtRing != NULL); | |
EvtTrb = (TRB_TEMPLATE *)(UINTN) EvtRing->EventRingDequeue; | |
*NewEvtTrb = (TRB_TEMPLATE *)(UINTN) EvtRing->EventRingDequeue; | |
if (EvtRing->EventRingDequeue == EvtRing->EventRingEnqueue) { | |
return EFI_NOT_READY; | |
} | |
Status = EFI_SUCCESS; | |
EvtRing->EventRingDequeue += sizeof (TRB_TEMPLATE); | |
// | |
// If the dequeue pointer is beyond the ring, then roll-back it to the begining of the ring. | |
// | |
if ((UINTN)EvtRing->EventRingDequeue >= ((UINTN) EvtRing->EventRingSeg0 + sizeof (TRB_TEMPLATE) * EvtRing->TrbNumber)) { | |
EvtRing->EventRingDequeue = EvtRing->EventRingSeg0; | |
} | |
return Status; | |
} | |
/** | |
Check if the Trb is a transaction of the URB. | |
@param Ring The transfer ring to be checked. | |
@param Trb The TRB to be checked. | |
@retval TRUE It is a transaction of the URB. | |
@retval FALSE It is not any transaction of the URB. | |
**/ | |
BOOLEAN | |
IsTrbInTrsRing ( | |
IN TRANSFER_RING *Ring, | |
IN TRB_TEMPLATE *Trb | |
) | |
{ | |
TRB_TEMPLATE *CheckedTrb; | |
UINTN Index; | |
CheckedTrb = (TRB_TEMPLATE *)(UINTN) Ring->RingSeg0; | |
ASSERT (Ring->TrbNumber == TR_RING_TRB_NUMBER); | |
for (Index = 0; Index < Ring->TrbNumber; Index++) { | |
if (Trb == CheckedTrb) { | |
return TRUE; | |
} | |
CheckedTrb++; | |
} | |
return FALSE; | |
} | |
/** | |
Check the URB's execution result and update the URB's | |
result accordingly. | |
@param Handle Debug port handle. | |
@param Urb The URB to check result. | |
**/ | |
VOID | |
XhcCheckUrbResult ( | |
IN USB3_DEBUG_PORT_HANDLE *Handle, | |
IN URB *Urb | |
) | |
{ | |
EVT_TRB_TRANSFER *EvtTrb; | |
TRB_TEMPLATE *TRBPtr; | |
UINTN Index; | |
EFI_STATUS Status; | |
URB *CheckedUrb; | |
UINT64 XhcDequeue; | |
UINT32 High; | |
UINT32 Low; | |
ASSERT ((Handle != NULL) && (Urb != NULL)); | |
if (Urb->Finished) { | |
goto EXIT; | |
} | |
EvtTrb = NULL; | |
// | |
// Traverse the event ring to find out all new events from the previous check. | |
// | |
XhcSyncEventRing (Handle, &Handle->EventRing); | |
for (Index = 0; Index < Handle->EventRing.TrbNumber; Index++) { | |
Status = XhcCheckNewEvent (Handle, &Handle->EventRing, ((TRB_TEMPLATE **)&EvtTrb)); | |
if (Status == EFI_NOT_READY) { | |
// | |
// All new events are handled, return directly. | |
// | |
goto EXIT; | |
} | |
if ((EvtTrb->Type != TRB_TYPE_COMMAND_COMPLT_EVENT) && (EvtTrb->Type != TRB_TYPE_TRANS_EVENT)) { | |
continue; | |
} | |
TRBPtr = (TRB_TEMPLATE *)(UINTN)(EvtTrb->TRBPtrLo | LShiftU64 ((UINT64) EvtTrb->TRBPtrHi, 32)); | |
if (IsTrbInTrsRing ((TRANSFER_RING *)(UINTN)(Urb->Ring), TRBPtr)) { | |
CheckedUrb = Urb; | |
} else if (IsTrbInTrsRing ((TRANSFER_RING *)(UINTN)(Handle->UrbIn.Ring), TRBPtr)) { | |
// | |
// If it is read event and it should be generated by poll, and current operation is write, we need save data into internal buffer. | |
// Internal buffer is used by next read. | |
// | |
Handle->DataCount = (UINT8) (Handle->UrbIn.DataLen - EvtTrb->Length); | |
CopyMem ((VOID *)(UINTN)Handle->Data, (VOID *)(UINTN)Handle->UrbIn.Data, Handle->DataCount); | |
// | |
// Fill this TRB complete with CycleBit, otherwise next read will fail with old TRB. | |
// | |
TRBPtr->CycleBit = (TRBPtr->CycleBit & BIT0) ? 0 : 1; | |
continue; | |
} else { | |
continue; | |
} | |
if ((EvtTrb->Completecode == TRB_COMPLETION_SHORT_PACKET) || | |
(EvtTrb->Completecode == TRB_COMPLETION_SUCCESS)) { | |
// | |
// The length of data which were transferred. | |
// | |
CheckedUrb->Completed += (CheckedUrb->DataLen - EvtTrb->Length); | |
} else { | |
CheckedUrb->Result |= EFI_USB_ERR_TIMEOUT; | |
} | |
// | |
// This Urb has been processed | |
// | |
CheckedUrb->Finished = TRUE; | |
} | |
EXIT: | |
// | |
// Advance event ring to last available entry | |
// | |
// Some 3rd party XHCI external cards don't support single 64-bytes width register access, | |
// So divide it to two 32-bytes width register access. | |
// | |
Low = XhcReadDebugReg (Handle, XHC_DC_DCERDP); | |
High = XhcReadDebugReg (Handle, XHC_DC_DCERDP + 4); | |
XhcDequeue = (UINT64)(LShiftU64((UINT64)High, 32) | Low); | |
if ((XhcDequeue & (~0x0F)) != ((UINT64)(UINTN)Handle->EventRing.EventRingDequeue & (~0x0F))) { | |
// | |
// Some 3rd party XHCI external cards don't support single 64-bytes width register access, | |
// So divide it to two 32-bytes width register access. | |
// | |
XhcWriteDebugReg (Handle, XHC_DC_DCERDP, XHC_LOW_32BIT (Handle->EventRing.EventRingDequeue)); | |
XhcWriteDebugReg (Handle, XHC_DC_DCERDP + 4, XHC_HIGH_32BIT (Handle->EventRing.EventRingDequeue)); | |
} | |
} | |
/** | |
Ring the door bell to notify XHCI there is a transaction to be executed. | |
@param Handle Debug port handle. | |
@param Urb The pointer to URB. | |
@retval EFI_SUCCESS Successfully ring the door bell. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
XhcRingDoorBell ( | |
IN USB3_DEBUG_PORT_HANDLE *Handle, | |
IN URB *Urb | |
) | |
{ | |
UINT32 Dcdb; | |
// | |
// 7.6.8.2 DCDB Register | |
// | |
Dcdb = (Urb->Direction == EfiUsbDataIn) ? 0x100 : 0x0; | |
XhcWriteDebugReg ( | |
Handle, | |
XHC_DC_DCDB, | |
Dcdb | |
); | |
return EFI_SUCCESS; | |
} | |
/** | |
Execute the transfer by polling the URB. This is a synchronous operation. | |
@param Handle Debug port handle. | |
@param Urb The URB to execute. | |
@param Timeout The time to wait before abort, in microsecond. | |
**/ | |
VOID | |
XhcExecTransfer ( | |
IN USB3_DEBUG_PORT_HANDLE *Handle, | |
IN URB *Urb, | |
IN UINTN Timeout | |
) | |
{ | |
TRANSFER_RING *Ring; | |
UINT64 Begin; | |
UINT64 TimeoutTicker; | |
UINT64 TimerRound; | |
TRB_TEMPLATE *Trb; | |
Begin = 0; | |
TimeoutTicker = 0; | |
TimerRound = 0; | |
XhcRingDoorBell (Handle, Urb); | |
if (Timeout != 0) { | |
Begin = GetPerformanceCounter (); | |
TimeoutTicker = DivU64x32 ( | |
MultU64x64 ( | |
Handle->TimerFrequency, | |
Timeout | |
), | |
1000000u | |
); | |
TimerRound = DivU64x64Remainder ( | |
TimeoutTicker, | |
DivU64x32 (Handle->TimerCycle, 2), | |
&TimeoutTicker | |
); | |
} | |
// | |
// Event Ring Not Empty bit can only be set to 1 by XHC after ringing door bell with some delay. | |
// | |
while (TRUE) { | |
if (Timeout != 0) { | |
if (TimerRound == 0) { | |
if (IsTimerTimeout (Handle, Begin, TimeoutTicker)) { | |
// | |
// If time out occurs. | |
// | |
Urb->Result |= EFI_USB_ERR_TIMEOUT; | |
break; | |
} | |
} else { | |
if (IsTimerTimeout (Handle, Begin, DivU64x32 (Handle->TimerCycle, 2))) { | |
TimerRound --; | |
} | |
} | |
} | |
XhcCheckUrbResult (Handle, Urb); | |
if (Urb->Finished) { | |
break; | |
} | |
} | |
// | |
// If URB transfer is error, restore transfer ring to original value before URB transfer | |
// This will make the current transfer TRB is always at the latest unused one in transfer ring. | |
// | |
Ring = (TRANSFER_RING *)(UINTN) Urb->Ring; | |
if ((Urb->Result != EFI_USB_NOERROR) && (Urb->Direction == EfiUsbDataIn)) { | |
// | |
// Adjust Enqueue pointer | |
// | |
Ring->RingEnqueue = Urb->Trb; | |
// | |
// Clear CCS flag for next use | |
// | |
Trb = (TRB_TEMPLATE *)(UINTN) Urb->Trb; | |
Trb->CycleBit = ((~Ring->RingPCS) & BIT0); | |
} else { | |
// | |
// Update transfer ring for next transfer. | |
// | |
XhcSyncTrsRing (Handle, Ring); | |
} | |
} | |
/** | |
Create a transfer TRB. | |
@param Handle Debug port handle. | |
@param Urb The urb used to construct the transfer TRB. | |
@return Created TRB or NULL | |
**/ | |
EFI_STATUS | |
XhcCreateTransferTrb ( | |
IN USB3_DEBUG_PORT_HANDLE *Handle, | |
IN URB *Urb | |
) | |
{ | |
TRANSFER_RING *EPRing; | |
TRB *Trb; | |
if (Urb->Direction == EfiUsbDataIn) { | |
EPRing = &Handle->TransferRingIn; | |
} else { | |
EPRing = &Handle->TransferRingOut; | |
} | |
Urb->Ring = (EFI_PHYSICAL_ADDRESS)(UINTN) EPRing; | |
XhcSyncTrsRing (Handle, EPRing); | |
Urb->Trb = EPRing->RingEnqueue; | |
Trb = (TRB *)(UINTN)EPRing->RingEnqueue; | |
Trb->TrbNormal.TRBPtrLo = XHC_LOW_32BIT (Urb->Data); | |
Trb->TrbNormal.TRBPtrHi = XHC_HIGH_32BIT (Urb->Data); | |
Trb->TrbNormal.Length = Urb->DataLen; | |
Trb->TrbNormal.TDSize = 0; | |
Trb->TrbNormal.IntTarget = 0; | |
Trb->TrbNormal.ISP = 1; | |
Trb->TrbNormal.IOC = 1; | |
Trb->TrbNormal.Type = TRB_TYPE_NORMAL; | |
// | |
// Update the cycle bit to indicate this TRB has been consumed. | |
// | |
Trb->TrbNormal.CycleBit = EPRing->RingPCS & BIT0; | |
return EFI_SUCCESS; | |
} | |
/** | |
Create a new URB for a new transaction. | |
@param Handle Debug port handle. | |
@param Direction The direction of data flow. | |
@param Data The user data to transfer | |
@param DataLen The length of data buffer | |
@return Created URB or NULL | |
**/ | |
URB* | |
XhcCreateUrb ( | |
IN USB3_DEBUG_PORT_HANDLE *Handle, | |
IN EFI_USB_DATA_DIRECTION Direction, | |
IN VOID *Data, | |
IN UINTN DataLen | |
) | |
{ | |
EFI_STATUS Status; | |
URB *Urb; | |
EFI_PHYSICAL_ADDRESS UrbData; | |
if (Direction == EfiUsbDataIn) { | |
Urb = &Handle->UrbIn; | |
} else { | |
Urb = &Handle->UrbOut; | |
} | |
UrbData = Urb->Data; | |
ZeroMem (Urb, sizeof (URB)); | |
Urb->Direction = Direction; | |
// | |
// Allocate memory to move data from CAR or SMRAM to normal memory | |
// to make XHCI DMA successfully | |
// re-use the pre-allocate buffer in PEI to avoid DXE memory service or gBS are not ready | |
// | |
Urb->Data = UrbData; | |
if (Direction == EfiUsbDataIn) { | |
// | |
// Do not break URB data in buffer as it may contain the data which were just put in via DMA by XHC | |
// | |
Urb->DataLen = (UINT32) DataLen; | |
} else { | |
// | |
// Put data into URB data out buffer which will create TRBs | |
// | |
ZeroMem ((VOID*)(UINTN) Urb->Data, DataLen); | |
CopyMem ((VOID*)(UINTN) Urb->Data, Data, DataLen); | |
Urb->DataLen = (UINT32) DataLen; | |
} | |
Status = XhcCreateTransferTrb (Handle, Urb); | |
ASSERT_EFI_ERROR (Status); | |
return Urb; | |
} | |
/** | |
Submits bulk transfer to a bulk endpoint of a USB device. | |
@param Handle Debug port handle. | |
@param Direction The direction of data transfer. | |
@param Data Array of pointers to the buffers of data to transmit | |
from or receive into. | |
@param DataLength The lenght of the data buffer. | |
@param Timeout Indicates the maximum time, in microsecond, which | |
the transfer is allowed to complete. | |
@retval EFI_SUCCESS The transfer was completed successfully. | |
@retval EFI_OUT_OF_RESOURCES The transfer failed due to lack of resource. | |
@retval EFI_INVALID_PARAMETER Some parameters are invalid. | |
@retval EFI_TIMEOUT The transfer failed due to timeout. | |
@retval EFI_DEVICE_ERROR The transfer failed due to host controller error. | |
**/ | |
EFI_STATUS | |
EFIAPI | |
XhcDataTransfer ( | |
IN USB3_DEBUG_PORT_HANDLE *Handle, | |
IN EFI_USB_DATA_DIRECTION Direction, | |
IN OUT VOID *Data, | |
IN OUT UINTN *DataLength, | |
IN UINTN Timeout | |
) | |
{ | |
URB *Urb; | |
EFI_STATUS Status; | |
// | |
// Validate the parameters | |
// | |
if ((DataLength == NULL) || (*DataLength == 0) || (Data == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Create a new URB, insert it into the asynchronous | |
// schedule list, then poll the execution status. | |
// | |
Urb = XhcCreateUrb (Handle, Direction, Data, *DataLength); | |
ASSERT (Urb != NULL); | |
XhcExecTransfer (Handle, Urb, Timeout); | |
*DataLength = Urb->Completed; | |
Status = EFI_TIMEOUT; | |
if (Urb->Result == EFI_USB_NOERROR) { | |
Status = EFI_SUCCESS; | |
} | |
if (Direction == EfiUsbDataIn) { | |
// | |
// Move data from internal buffer to outside buffer (outside buffer may be in SMRAM...) | |
// SMRAM does not allow to do DMA, so we create an internal buffer. | |
// | |
CopyMem (Data, (VOID *)(UINTN)Urb->Data, *DataLength); | |
} | |
return Status; | |
} | |