/** @file Debug Port Library implementation based on usb3 debug port. Copyright (c) 2014 - 2018, Intel Corporation. All rights reserved.
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; }