mirror of https://github.com/acidanthera/audk.git
584 lines
16 KiB
C
584 lines
16 KiB
C
/** @file
|
|
Debug Port Library implementation based on usb3 debug port.
|
|
|
|
Copyright (c) 2014 - 2018, 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;
|
|
|
|
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 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 += (((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 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);
|
|
|
|
//
|
|
// 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;
|
|
}
|
|
|