blob: a651e8827112fe85fa0eea6a8e671398006734fa [file] [log] [blame]
/** @file
Debug Port Library implementation based on usb3 debug port.
Copyright (c) 2014 - 2018, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#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;
ASSERT (EvtRing != NULL);
*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 beginning 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 += (((TRANSFER_TRB_NORMAL *)TRBPtr)->Length - 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;
TRB_TEMPLATE *Trb;
UINTN Loop;
UINTN Index;
Loop = Timeout / XHC_DEBUG_PORT_1_MILLISECOND;
if (Timeout == 0) {
Loop = 0xFFFFFFFF;
}
XhcRingDoorBell (Handle, Urb);
//
// Event Ring Not Empty bit can only be set to 1 by XHC after ringing door bell with some delay.
//
for (Index = 0; Index < Loop; Index++) {
XhcCheckUrbResult (Handle, Urb);
if (Urb->Finished) {
break;
}
MicroSecondDelay (XHC_DEBUG_PORT_1_MILLISECOND);
}
if (Index == Loop) {
//
// If time out occurs.
//
Urb->Result |= EFI_USB_ERR_TIMEOUT;
}
//
// 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 length 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);
//
// Make sure the data received from HW can fit in the received buffer.
//
if (Urb->Completed > *DataLength) {
return EFI_DEVICE_ERROR;
}
*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;
}