From af1a266670d040d2f4083ff309d732d648afba2a Mon Sep 17 00:00:00 2001 From: Angelos Mouzakitis Date: Tue, 10 Oct 2023 14:33:42 +0000 Subject: Add submodule dependency files Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec --- roms/edk2/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c | 463 +++++++++++++++++++++ 1 file changed, 463 insertions(+) create mode 100644 roms/edk2/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c (limited to 'roms/edk2/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c') diff --git a/roms/edk2/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c b/roms/edk2/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c new file mode 100644 index 000000000..311f50198 --- /dev/null +++ b/roms/edk2/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c @@ -0,0 +1,463 @@ +/** @file +PEIM to produce gPeiUsb2HostControllerPpiGuid based on gPeiUsbControllerPpiGuid +which is used to enable recovery function from USB Drivers. + +Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.
+Copyright (c) Microsoft Corporation.
+ +SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "EhcPeim.h" + +/** + Create helper QTD/QH for the EHCI device. + + @param Ehc The EHCI device. + + @retval EFI_OUT_OF_RESOURCES Failed to allocate resource for helper QTD/QH. + @retval EFI_SUCCESS Helper QH/QTD are created. + +**/ +EFI_STATUS +EhcCreateHelpQ ( + IN PEI_USB2_HC_DEV *Ehc + ) +{ + USB_ENDPOINT Ep; + PEI_EHC_QH *Qh; + QH_HW *QhHw; + PEI_EHC_QTD *Qtd; + + // + // Create an inactive Qtd to terminate the short packet read. + // + Qtd = EhcCreateQtd (Ehc, NULL, 0, QTD_PID_INPUT, 0, 64); + + if (Qtd == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Qtd->QtdHw.Status = QTD_STAT_HALTED; + Ehc->ShortReadStop = Qtd; + + // + // Create a QH to act as the EHC reclamation header. + // Set the header to loopback to itself. + // + Ep.DevAddr = 0; + Ep.EpAddr = 1; + Ep.Direction = EfiUsbDataIn; + Ep.DevSpeed = EFI_USB_SPEED_HIGH; + Ep.MaxPacket = 64; + Ep.HubAddr = 0; + Ep.HubPort = 0; + Ep.Toggle = 0; + Ep.Type = EHC_BULK_TRANSFER; + Ep.PollRate = 1; + + Qh = EhcCreateQh (Ehc, &Ep); + + if (Qh == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + QhHw = &Qh->QhHw; + QhHw->HorizonLink = QH_LINK (QhHw, EHC_TYPE_QH, FALSE); + QhHw->Status = QTD_STAT_HALTED; + QhHw->ReclaimHead = 1; + Ehc->ReclaimHead = Qh; + + // + // Create a dummy QH to act as the terminator for periodical schedule + // + Ep.EpAddr = 2; + Ep.Type = EHC_INT_TRANSFER_SYNC; + + Qh = EhcCreateQh (Ehc, &Ep); + + if (Qh == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Qh->QhHw.Status = QTD_STAT_HALTED; + Ehc->PeriodOne = Qh; + + return EFI_SUCCESS; +} + +/** + Initialize the schedule data structure such as frame list. + + @param Ehc The EHCI device to init schedule data for. + + @retval EFI_OUT_OF_RESOURCES Failed to allocate resource to init schedule data. + @retval EFI_SUCCESS The schedule data is initialized. + +**/ +EFI_STATUS +EhcInitSched ( + IN PEI_USB2_HC_DEV *Ehc + ) +{ + VOID *Buf; + EFI_PHYSICAL_ADDRESS PhyAddr; + VOID *Map; + UINTN Index; + UINT32 *Desc; + EFI_STATUS Status; + EFI_PHYSICAL_ADDRESS PciAddr; + + // + // First initialize the periodical schedule data: + // 1. Allocate and map the memory for the frame list + // 2. Create the help QTD/QH + // 3. Initialize the frame entries + // 4. Set the frame list register + // + // + // The Frame List ocupies 4K bytes, + // and must be aligned on 4-Kbyte boundaries. + // + Status = IoMmuAllocateBuffer ( + Ehc->IoMmu, + 1, + &Buf, + &PhyAddr, + &Map + ); + + if (EFI_ERROR (Status) || (Buf == NULL)) { + return EFI_OUT_OF_RESOURCES; + } + + Ehc->PeriodFrame = Buf; + Ehc->PeriodFrameMap = Map; + Ehc->High32bitAddr = EHC_HIGH_32BIT (PhyAddr); + + // + // Init memory pool management then create the helper + // QTD/QH. If failed, previously allocated resources + // will be freed by EhcFreeSched + // + Ehc->MemPool = UsbHcInitMemPool ( + Ehc, + EHC_BIT_IS_SET (Ehc->HcCapParams, HCCP_64BIT), + Ehc->High32bitAddr + ); + + if (Ehc->MemPool == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = EhcCreateHelpQ (Ehc); + + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Initialize the frame list entries then set the registers + // + Desc = (UINT32 *) Ehc->PeriodFrame; + PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->PeriodOne, sizeof (PEI_EHC_QH)); + for (Index = 0; Index < EHC_FRAME_LEN; Index++) { + Desc[Index] = QH_LINK (PciAddr, EHC_TYPE_QH, FALSE); + } + + EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, EHC_LOW_32BIT (PhyAddr)); + + // + // Second initialize the asynchronous schedule: + // Only need to set the AsynListAddr register to + // the reclamation header + // + PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH)); + EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, EHC_LOW_32BIT (PciAddr)); + return EFI_SUCCESS; +} + +/** + Free the schedule data. It may be partially initialized. + + @param Ehc The EHCI device. + +**/ +VOID +EhcFreeSched ( + IN PEI_USB2_HC_DEV *Ehc + ) +{ + EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, 0); + EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, 0); + + if (Ehc->PeriodOne != NULL) { + UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->PeriodOne, sizeof (PEI_EHC_QH)); + Ehc->PeriodOne = NULL; + } + + if (Ehc->ReclaimHead != NULL) { + UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH)); + Ehc->ReclaimHead = NULL; + } + + if (Ehc->ShortReadStop != NULL) { + UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->ShortReadStop, sizeof (PEI_EHC_QTD)); + Ehc->ShortReadStop = NULL; + } + + if (Ehc->MemPool != NULL) { + UsbHcFreeMemPool (Ehc, Ehc->MemPool); + Ehc->MemPool = NULL; + } + + if (Ehc->PeriodFrame != NULL) { + IoMmuFreeBuffer (Ehc->IoMmu, 1, Ehc->PeriodFrame, Ehc->PeriodFrameMap); + Ehc->PeriodFrame = NULL; + } +} + +/** + Link the queue head to the asynchronous schedule list. + UEFI only supports one CTRL/BULK transfer at a time + due to its interfaces. This simplifies the AsynList + management: A reclamation header is always linked to + the AsyncListAddr, the only active QH is appended to it. + + @param Ehc The EHCI device. + @param Qh The queue head to link. + +**/ +VOID +EhcLinkQhToAsync ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_EHC_QH *Qh + ) +{ + PEI_EHC_QH *Head; + + // + // Append the queue head after the reclaim header, then + // fix the hardware visiable parts (EHCI R1.0 page 72). + // ReclaimHead is always linked to the EHCI's AsynListAddr. + // + Head = Ehc->ReclaimHead; + + Qh->NextQh = Head->NextQh; + Head->NextQh = Qh; + + Qh->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE);; + Head->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE); +} + +/** + Unlink a queue head from the asynchronous schedule list. + Need to synchronize with hardware. + + @param Ehc The EHCI device. + @param Qh The queue head to unlink. + +**/ +VOID +EhcUnlinkQhFromAsync ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_EHC_QH *Qh + ) +{ + PEI_EHC_QH *Head; + + ASSERT (Ehc->ReclaimHead->NextQh == Qh); + + // + // Remove the QH from reclamation head, then update the hardware + // visiable part: Only need to loopback the ReclaimHead. The Qh + // is pointing to ReclaimHead (which is staill in the list). + // + Head = Ehc->ReclaimHead; + + Head->NextQh = Qh->NextQh; + Qh->NextQh = NULL; + + Head->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE); + + // + // Set and wait the door bell to synchronize with the hardware + // + EhcSetAndWaitDoorBell (Ehc, EHC_GENERIC_TIMEOUT); + + return; +} + +/** + Check the URB's execution result and update the URB's + result accordingly. + + @param Ehc The EHCI device. + @param Urb The URB to check result. + + @retval TRUE URB transfer is finialized. + @retval FALSE URB transfer is not finialized. + +**/ +BOOLEAN +EhcCheckUrbResult ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_URB *Urb + ) +{ + EFI_LIST_ENTRY *Entry; + PEI_EHC_QTD *Qtd; + QTD_HW *QtdHw; + UINT8 State; + BOOLEAN Finished; + + ASSERT ((Ehc != NULL) && (Urb != NULL) && (Urb->Qh != NULL)); + + Finished = TRUE; + Urb->Completed = 0; + + Urb->Result = EFI_USB_NOERROR; + + if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { + Urb->Result |= EFI_USB_ERR_SYSTEM; + goto ON_EXIT; + } + + BASE_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) { + Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList); + QtdHw = &Qtd->QtdHw; + State = (UINT8) QtdHw->Status; + + if (EHC_BIT_IS_SET (State, QTD_STAT_HALTED)) { + // + // EHCI will halt the queue head when met some error. + // If it is halted, the result of URB is finialized. + // + if ((State & QTD_STAT_ERR_MASK) == 0) { + Urb->Result |= EFI_USB_ERR_STALL; + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_BABBLE_ERR)) { + Urb->Result |= EFI_USB_ERR_BABBLE; + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_BUFF_ERR)) { + Urb->Result |= EFI_USB_ERR_BUFFER; + } + + if (EHC_BIT_IS_SET (State, QTD_STAT_TRANS_ERR) && (QtdHw->ErrCnt == 0)) { + Urb->Result |= EFI_USB_ERR_TIMEOUT; + } + + Finished = TRUE; + goto ON_EXIT; + + } else if (EHC_BIT_IS_SET (State, QTD_STAT_ACTIVE)) { + // + // The QTD is still active, no need to check furthur. + // + Urb->Result |= EFI_USB_ERR_NOTEXECUTE; + + Finished = FALSE; + goto ON_EXIT; + + } else { + // + // This QTD is finished OK or met short packet read. Update the + // transfer length if it isn't a setup. + // + if (QtdHw->Pid != QTD_PID_SETUP) { + Urb->Completed += Qtd->DataLen - QtdHw->TotalBytes; + } + + if ((QtdHw->TotalBytes != 0) && (QtdHw->Pid == QTD_PID_INPUT)) { + //EHC_DUMP_QH ((Urb->Qh, "Short packet read", FALSE)); + + // + // Short packet read condition. If it isn't a setup transfer, + // no need to check furthur: the queue head will halt at the + // ShortReadStop. If it is a setup transfer, need to check the + // Status Stage of the setup transfer to get the finial result + // + if (QtdHw->AltNext == QTD_LINK (Ehc->ShortReadStop, FALSE)) { + + Finished = TRUE; + goto ON_EXIT; + } + } + } + } + +ON_EXIT: + // + // Return the data toggle set by EHCI hardware, bulk and interrupt + // transfer will use this to initialize the next transaction. For + // Control transfer, it always start a new data toggle sequence for + // new transfer. + // + // NOTICE: don't move DT update before the loop, otherwise there is + // a race condition that DT is wrong. + // + Urb->DataToggle = (UINT8) Urb->Qh->QhHw.DataToggle; + + return Finished; +} + +/** + Execute the transfer by polling the URB. This is a synchronous operation. + + @param Ehc The EHCI device. + @param Urb The URB to execute. + @param TimeOut The time to wait before abort, in millisecond. + + @retval EFI_DEVICE_ERROR The transfer failed due to transfer error. + @retval EFI_TIMEOUT The transfer failed due to time out. + @retval EFI_SUCCESS The transfer finished OK. + +**/ +EFI_STATUS +EhcExecTransfer ( + IN PEI_USB2_HC_DEV *Ehc, + IN PEI_URB *Urb, + IN UINTN TimeOut + ) +{ + EFI_STATUS Status; + UINTN Index; + UINTN Loop; + BOOLEAN Finished; + BOOLEAN InfiniteLoop; + + Status = EFI_SUCCESS; + Loop = TimeOut * EHC_1_MILLISECOND; + Finished = FALSE; + InfiniteLoop = FALSE; + + // + // If Timeout is 0, then the caller must wait for the function to be completed + // until EFI_SUCCESS or EFI_DEVICE_ERROR is returned. + // + if (TimeOut == 0) { + InfiniteLoop = TRUE; + } + + for (Index = 0; InfiniteLoop || (Index < Loop); Index++) { + Finished = EhcCheckUrbResult (Ehc, Urb); + + if (Finished) { + break; + } + + MicroSecondDelay (EHC_1_MICROSECOND); + } + + if (!Finished) { + Status = EFI_TIMEOUT; + } else if (Urb->Result != EFI_USB_NOERROR) { + Status = EFI_DEVICE_ERROR; + } + + return Status; +} + -- cgit 1.2.3-korg