summaryrefslogtreecommitdiff
path: root/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c
diff options
context:
space:
mode:
Diffstat (limited to 'MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c')
-rw-r--r--MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c873
1 files changed, 873 insertions, 0 deletions
diff --git a/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c b/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c
new file mode 100644
index 0000000000..d153aa39dc
--- /dev/null
+++ b/MdeModulePkg/Bus/Pci/EhciPei/EhciSched.c
@@ -0,0 +1,873 @@
+/** @file
+PEIM to produce gPeiUsb2HostControllerPpiGuid based on gPeiUsbControllerPpiGuid
+which is used to enable recovery function from USB Drivers.
+
+Copyright (c) 2010, Intel Corporation. All rights reserved.<BR>
+
+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 "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
+ )
+{
+ EFI_PHYSICAL_ADDRESS PhyAddr;
+ VOID *Map;
+ UINTN Index;
+ UINT32 *Desc;
+ EFI_STATUS Status;
+
+ //
+ // 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 = PeiServicesAllocatePages (
+ EfiBootServicesCode,
+ 1,
+ &PhyAddr
+ );
+
+ Map = NULL;
+ Ehc->PeriodFrameHost = (VOID *)(UINTN)PhyAddr;
+ Ehc->PeriodFrame = (VOID *)(UINTN)PhyAddr;
+ 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;
+
+ for (Index = 0; Index < EHC_FRAME_LEN; Index++) {
+ Desc[Index] = QH_LINK (Ehc->PeriodOne, EHC_TYPE_QH, FALSE);
+ }
+
+ EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, EHC_LOW_32BIT (Ehc->PeriodFrame));
+
+ //
+ // Second initialize the asynchronous schedule:
+ // Only need to set the AsynListAddr register to
+ // the reclamation header
+ //
+ EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, EHC_LOW_32BIT (Ehc->ReclaimHead));
+ 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->MemPool, Ehc->PeriodOne, sizeof (PEI_EHC_QH));
+ Ehc->PeriodOne = NULL;
+ }
+
+ if (Ehc->ReclaimHead != NULL) {
+ UsbHcFreeMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH));
+ Ehc->ReclaimHead = NULL;
+ }
+
+ if (Ehc->ShortReadStop != NULL) {
+ UsbHcFreeMem (Ehc->MemPool, Ehc->ShortReadStop, sizeof (PEI_EHC_QTD));
+ Ehc->ShortReadStop = NULL;
+ }
+
+ if (Ehc->MemPool != NULL) {
+ UsbHcFreeMemPool (Ehc->MemPool);
+ Ehc->MemPool = NULL;
+ }
+
+ if (Ehc->PeriodFrame != NULL) {
+ 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;
+ EFI_STATUS Status;
+
+ 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
+ //
+ Status = EhcSetAndWaitDoorBell (Ehc, EHC_GENERIC_TIMEOUT);
+
+ return;
+}
+
+/**
+ Link a queue head for interrupt transfer to the periodic
+ schedule frame list. This code is very much the same as
+ that in UHCI.
+
+ @param Ehc The EHCI device.
+ @param Qh The queue head to link.
+
+**/
+VOID
+EhcLinkQhToPeriod (
+ IN PEI_USB2_HC_DEV *Ehc,
+ IN PEI_EHC_QH *Qh
+ )
+{
+ UINT32 *Frames;
+ UINTN Index;
+ PEI_EHC_QH *Prev;
+ PEI_EHC_QH *Next;
+
+ Frames = Ehc->PeriodFrame;
+
+ for (Index = 0; Index < EHC_FRAME_LEN; Index += Qh->Interval) {
+ //
+ // First QH can't be NULL because we always keep PeriodOne
+ // heads on the frame list
+ //
+ ASSERT (!EHC_LINK_TERMINATED (Frames[Index]));
+ Next = EHC_ADDR (Ehc->High32bitAddr, Frames[Index]);
+ Prev = NULL;
+
+ //
+ // Now, insert the queue head (Qh) into this frame:
+ // 1. Find a queue head with the same poll interval, just insert
+ // Qh after this queue head, then we are done.
+ //
+ // 2. Find the position to insert the queue head into:
+ // Previous head's interval is bigger than Qh's
+ // Next head's interval is less than Qh's
+ // Then, insert the Qh between then
+ //
+ while (Next->Interval > Qh->Interval) {
+ Prev = Next;
+ Next = Next->NextQh;
+ }
+
+ ASSERT (Next != NULL);
+
+ //
+ // The entry may have been linked into the frame by early insertation.
+ // For example: if insert a Qh with Qh.Interval == 4, and there is a Qh
+ // with Qh.Interval == 8 on the frame. If so, we are done with this frame.
+ // It isn't necessary to compare all the QH with the same interval to
+ // Qh. This is because if there is other QH with the same interval, Qh
+ // should has been inserted after that at Frames[0] and at Frames[0] it is
+ // impossible for (Next == Qh)
+ //
+ if (Next == Qh) {
+ continue;
+ }
+
+ if (Next->Interval == Qh->Interval) {
+ //
+ // If there is a QH with the same interval, it locates at
+ // Frames[0], and we can simply insert it after this QH. We
+ // are all done.
+ //
+ ASSERT ((Index == 0) && (Qh->NextQh == NULL));
+
+ Prev = Next;
+ Next = Next->NextQh;
+
+ Qh->NextQh = Next;
+ Prev->NextQh = Qh;
+
+ Qh->QhHw.HorizonLink = Prev->QhHw.HorizonLink;
+ Prev->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE);
+ break;
+ }
+
+ //
+ // OK, find the right position, insert it in. If Qh's next
+ // link has already been set, it is in position. This is
+ // guarranted by 2^n polling interval.
+ //
+ if (Qh->NextQh == NULL) {
+ Qh->NextQh = Next;
+ Qh->QhHw.HorizonLink = QH_LINK (Next, EHC_TYPE_QH, FALSE);
+ }
+
+ if (Prev == NULL) {
+ Frames[Index] = QH_LINK (Qh, EHC_TYPE_QH, FALSE);
+ } else {
+ Prev->NextQh = Qh;
+ Prev->QhHw.HorizonLink = QH_LINK (Qh, EHC_TYPE_QH, FALSE);
+ }
+ }
+}
+
+/**
+ Unlink an interrupt queue head from the periodic
+ schedule frame list.
+
+ @param Ehc The EHCI device.
+ @param Qh The queue head to unlink.
+
+**/
+VOID
+EhcUnlinkQhFromPeriod (
+ IN PEI_USB2_HC_DEV *Ehc,
+ IN PEI_EHC_QH *Qh
+ )
+{
+ UINT32 *Frames;
+ UINTN Index;
+ PEI_EHC_QH *Prev;
+ PEI_EHC_QH *This;
+
+ Frames = Ehc->PeriodFrame;
+
+ for (Index = 0; Index < EHC_FRAME_LEN; Index += Qh->Interval) {
+ //
+ // Frame link can't be NULL because we always keep PeroidOne
+ // on the frame list
+ //
+ ASSERT (!EHC_LINK_TERMINATED (Frames[Index]));
+ This = EHC_ADDR (Ehc->High32bitAddr, Frames[Index]);
+ Prev = NULL;
+
+ //
+ // Walk through the frame's QH list to find the
+ // queue head to remove
+ //
+ while ((This != NULL) && (This != Qh)) {
+ Prev = This;
+ This = This->NextQh;
+ }
+
+ //
+ // Qh may have already been unlinked from this frame
+ // by early action. See the comments in EhcLinkQhToPeriod.
+ //
+ if (This == NULL) {
+ continue;
+ }
+
+ if (Prev == NULL) {
+ //
+ // Qh is the first entry in the frame
+ //
+ Frames[Index] = Qh->QhHw.HorizonLink;
+ } else {
+ Prev->NextQh = Qh->NextQh;
+ Prev->QhHw.HorizonLink = Qh->QhHw.HorizonLink;
+ }
+ }
+}
+
+/**
+ 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;
+ }
+
+ EFI_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;
+
+ Status = EFI_SUCCESS;
+ Loop = (TimeOut * EHC_1_MILLISECOND / EHC_SYNC_POLL_INTERVAL) + 1;
+ Finished = FALSE;
+
+ for (Index = 0; Index < Loop; Index++) {
+ Finished = EhcCheckUrbResult (Ehc, Urb);
+
+ if (Finished) {
+ break;
+ }
+
+ MicroSecondDelay (EHC_SYNC_POLL_INTERVAL);
+ }
+
+ if (!Finished) {
+ Status = EFI_TIMEOUT;
+ } else if (Urb->Result != EFI_USB_NOERROR) {
+ Status = EFI_DEVICE_ERROR;
+ }
+
+ return Status;
+}
+
+/**
+ Delete a single asynchronous interrupt transfer for
+ the device and endpoint.
+
+ @param Ehc The EHCI device.
+ @param DevAddr The address of the target device.
+ @param EpNum The endpoint of the target.
+ @param DataToggle Return the next data toggle to use.
+
+ @retval EFI_NOT_FOUND No transfer for the device is found.
+ @retval EFI_SUCCESS An asynchronous transfer is removed.
+
+**/
+EFI_STATUS
+EhciDelAsyncIntTransfer (
+ IN PEI_USB2_HC_DEV *Ehc,
+ IN UINT8 DevAddr,
+ IN UINT8 EpNum,
+ OUT UINT8 *DataToggle
+ )
+{
+ EFI_LIST_ENTRY *Entry;
+ EFI_LIST_ENTRY *Next;
+ PEI_URB *Urb;
+ EFI_USB_DATA_DIRECTION Direction;
+
+ Direction = (((EpNum & 0x80) != 0) ? EfiUsbDataIn : EfiUsbDataOut);
+ EpNum &= 0x0F;
+
+ EFI_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) {
+ Urb = EFI_LIST_CONTAINER (Entry, PEI_URB, UrbList);
+
+ if ((Urb->Ep.DevAddr == DevAddr) && (Urb->Ep.EpAddr == EpNum) &&
+ (Urb->Ep.Direction == Direction)) {
+ //
+ // Check the URB status to retrieve the next data toggle
+ // from the associated queue head.
+ //
+ EhcCheckUrbResult (Ehc, Urb);
+ *DataToggle = Urb->DataToggle;
+
+ EhcUnlinkQhFromPeriod (Ehc, Urb->Qh);
+ RemoveEntryList (&Urb->UrbList);
+
+ EhcFreeUrb (Ehc, Urb);
+ return EFI_SUCCESS;
+ }
+ }
+
+ return EFI_NOT_FOUND;
+}
+
+/**
+ Remove all the asynchronous interrutp transfers.
+
+ @param Ehc The EHCI device.
+
+**/
+VOID
+EhciDelAllAsyncIntTransfers (
+ IN PEI_USB2_HC_DEV *Ehc
+ )
+{
+ EFI_LIST_ENTRY *Entry;
+ EFI_LIST_ENTRY *Next;
+ PEI_URB *Urb;
+
+ EFI_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) {
+ Urb = EFI_LIST_CONTAINER (Entry, PEI_URB, UrbList);
+
+ EhcUnlinkQhFromPeriod (Ehc, Urb->Qh);
+ RemoveEntryList (&Urb->UrbList);
+
+ EhcFreeUrb (Ehc, Urb);
+ }
+}
+
+/**
+ Flush data from PCI controller specific address to mapped system
+ memory address.
+
+ @param Ehc The EHCI device.
+ @param Urb The URB to unmap.
+
+ @retval EFI_DEVICE_ERROR Fail to flush data to mapped system memory.
+ @retval EFI_SUCCESS Success to flush data to mapped system memory.
+
+**/
+EFI_STATUS
+EhcFlushAsyncIntMap (
+ IN PEI_USB2_HC_DEV *Ehc,
+ IN PEI_URB *Urb
+ )
+{
+ EFI_PHYSICAL_ADDRESS PhyAddr;
+
+ Urb->DataMap = NULL;
+ PhyAddr = (EFI_PHYSICAL_ADDRESS) (UINTN) Urb->Data;
+ Urb->DataPhy = (VOID *) ((UINTN) PhyAddr);
+ return EFI_SUCCESS;
+}
+
+/**
+ Update the queue head for next round of asynchronous transfer.
+
+ @param Urb The URB to update.
+
+**/
+VOID
+EhcUpdateAsyncRequest (
+ IN PEI_URB *Urb
+ )
+{
+ EFI_LIST_ENTRY *Entry;
+ PEI_EHC_QTD *FirstQtd;
+ QH_HW *QhHw;
+ PEI_EHC_QTD *Qtd;
+ QTD_HW *QtdHw;
+ UINTN Index;
+
+ Qtd = NULL;
+
+ if (Urb->Result == EFI_USB_NOERROR) {
+ FirstQtd = NULL;
+
+ EFI_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) {
+ Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList);
+
+ if (FirstQtd == NULL) {
+ FirstQtd = Qtd;
+ }
+
+ //
+ // Update the QTD for next round of transfer. Host control
+ // may change dt/Total Bytes to Transfer/C_Page/Cerr/Status/
+ // Current Offset. These fields need to be updated. DT isn't
+ // used by interrupt transfer. It uses DT in queue head.
+ // Current Offset is in Page[0], only need to reset Page[0]
+ // to initial data buffer.
+ //
+ QtdHw = &Qtd->QtdHw;
+ QtdHw->Status = QTD_STAT_ACTIVE;
+ QtdHw->ErrCnt = QTD_MAX_ERR;
+ QtdHw->CurPage = 0;
+ QtdHw->TotalBytes = (UINT32) Qtd->DataLen;
+ QtdHw->Page[0] = EHC_LOW_32BIT (Qtd->Data);
+ }
+
+ //
+ // Update QH for next round of transfer. Host control only
+ // touch the fields in transfer overlay area. Only need to
+ // zero out the overlay area and set NextQtd to the first
+ // QTD. DateToggle bit is left untouched.
+ //
+ QhHw = &Urb->Qh->QhHw;
+ QhHw->CurQtd = QTD_LINK (0, TRUE);
+ QhHw->AltQtd = 0;
+
+ QhHw->Status = 0;
+ QhHw->Pid = 0;
+ QhHw->ErrCnt = 0;
+ QhHw->CurPage = 0;
+ QhHw->Ioc = 0;
+ QhHw->TotalBytes = 0;
+
+ for (Index = 0; Index < 5; Index++) {
+ QhHw->Page[Index] = 0;
+ QhHw->PageHigh[Index] = 0;
+ }
+
+ QhHw->NextQtd = QTD_LINK (FirstQtd, FALSE);
+ }
+
+ return ;
+}
+
+/**
+ Remove all the asynchronous interrutp transfers.
+
+ @param Event Interrupt event.
+ @param Context Pointer to PEI_USB2_HC_DEV.
+
+**/
+VOID
+EFIAPI
+EhcMoniteAsyncRequests (
+ IN EFI_EVENT Event,
+ IN VOID *Context
+ )
+{
+ PEI_USB2_HC_DEV *Ehc;
+ EFI_LIST_ENTRY *Entry;
+ EFI_LIST_ENTRY *Next;
+ BOOLEAN Finished;
+ UINT8 *ProcBuf;
+ PEI_URB *Urb;
+ EFI_STATUS Status;
+ UINTN PageNumber;
+
+ Ehc = (PEI_USB2_HC_DEV *) Context;
+
+ EFI_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) {
+ Urb = EFI_LIST_CONTAINER (Entry, PEI_URB, UrbList);
+
+ //
+ // Check the result of URB execution. If it is still
+ // active, check the next one.
+ //
+ Finished = EhcCheckUrbResult (Ehc, Urb);
+
+ if (!Finished) {
+ continue;
+ }
+
+ //
+ // Flush any PCI posted write transactions from a PCI host
+ // bridge to system memory.
+ //
+ Status = EhcFlushAsyncIntMap (Ehc, Urb);
+
+ //
+ // Allocate a buffer then copy the transferred data for user.
+ // If failed to allocate the buffer, update the URB for next
+ // round of transfer. Ignore the data of this round.
+ //
+ ProcBuf = NULL;
+
+ if (Urb->Result == EFI_USB_NOERROR) {
+ ASSERT (Urb->Completed <= Urb->DataLen);
+ PageNumber = Urb->Completed/PAGESIZE +1;
+ Status = PeiServicesAllocatePages (
+ EfiBootServicesCode,
+ PageNumber,
+ (EFI_PHYSICAL_ADDRESS *)ProcBuf
+ );
+ if (ProcBuf == NULL) {
+ EhcUpdateAsyncRequest (Urb);
+ continue;
+ }
+
+ CopyMem (ProcBuf, Urb->Data, Urb->Completed);
+ }
+
+ EhcUpdateAsyncRequest (Urb);
+
+ //
+ // Leave error recovery to its related device driver. A
+ // common case of the error recovery is to re-submit the
+ // interrupt transfer which is linked to the head of the
+ // list. This function scans from head to tail. So the
+ // re-submitted interrupt transfer's callback function
+ // will not be called again in this round. Don't touch this
+ // URB after the callback, it may have been removed by the
+ // callback.
+ //
+ if (Urb->Callback != NULL) {
+ (Urb->Callback) (ProcBuf, Urb->Completed, Urb->Context, Urb->Result);
+ }
+
+ if (ProcBuf != NULL) {
+ }
+ }
+}