summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c')
-rw-r--r--src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c463
1 files changed, 463 insertions, 0 deletions
diff --git a/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c b/src/VBox/Devices/EFI/Firmware/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c
new file mode 100644
index 00000000..d0b4c42d
--- /dev/null
+++ b/src/VBox/Devices/EFI/Firmware/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.<BR>
+Copyright (c) Microsoft Corporation.<BR>
+
+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;
+}
+