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