| /** @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; | |
| } |