From 2cb6eabe0b358fe0d4e4dd602f84b5f5d3e39a6c Mon Sep 17 00:00:00 2001 From: Elvin Li Date: Wed, 22 Oct 2014 05:01:58 +0000 Subject: Add DebugCommunicationLibUsb3 for USB3.0 source level debug support. Contributed-under: TianoCore Contribution Agreement 1.0 Signed-off-by: Elvin Li Reviewed-by: Ruiyu Ni Reviewed-by: Feng Tian Reviewed-by: Jeff Fan git-svn-id: https://svn.code.sf.net/p/edk2/code/trunk/edk2@16224 6f19259b-4bc3-4df7-8a09-765794883524 --- .../DebugCommunicationLibUsb3Transfer.c | 606 +++++++++++++++++++++ 1 file changed, 606 insertions(+) create mode 100644 SourceLevelDebugPkg/Library/DebugCommunicationLibUsb3/DebugCommunicationLibUsb3Transfer.c (limited to 'SourceLevelDebugPkg/Library/DebugCommunicationLibUsb3/DebugCommunicationLibUsb3Transfer.c') diff --git a/SourceLevelDebugPkg/Library/DebugCommunicationLibUsb3/DebugCommunicationLibUsb3Transfer.c b/SourceLevelDebugPkg/Library/DebugCommunicationLibUsb3/DebugCommunicationLibUsb3Transfer.c new file mode 100644 index 0000000000..73963e1199 --- /dev/null +++ b/SourceLevelDebugPkg/Library/DebugCommunicationLibUsb3/DebugCommunicationLibUsb3Transfer.c @@ -0,0 +1,606 @@ +/** @file + Debug Port Library implementation based on usb3 debug port. + + Copyright (c) 2014, Intel Corporation. All rights reserved.
+ 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 (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 = (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; +} + -- cgit v1.2.3