aboutsummaryrefslogtreecommitdiffstats
path: root/roms/edk2/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/edk2/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.c')
-rw-r--r--roms/edk2/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.c1040
1 files changed, 1040 insertions, 0 deletions
diff --git a/roms/edk2/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.c b/roms/edk2/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.c
new file mode 100644
index 000000000..e21641884
--- /dev/null
+++ b/roms/edk2/MdeModulePkg/Bus/Pci/UhciDxe/UhciSched.c
@@ -0,0 +1,1040 @@
+/** @file
+
+ The EHCI register operation routines.
+
+Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR>
+SPDX-License-Identifier: BSD-2-Clause-Patent
+
+**/
+
+#include "Uhci.h"
+
+
+/**
+ Create Frame List Structure.
+
+ @param Uhc UHCI device.
+
+ @retval EFI_OUT_OF_RESOURCES Can't allocate memory resources.
+ @retval EFI_UNSUPPORTED Map memory fail.
+ @retval EFI_SUCCESS Success.
+
+**/
+EFI_STATUS
+UhciInitFrameList (
+ IN USB_HC_DEV *Uhc
+ )
+{
+ EFI_PHYSICAL_ADDRESS MappedAddr;
+ EFI_STATUS Status;
+ VOID *Buffer;
+ VOID *Mapping;
+ UINTN Pages;
+ UINTN Bytes;
+ UINTN Index;
+ EFI_PHYSICAL_ADDRESS PhyAddr;
+
+ //
+ // The Frame List is a common buffer that will be
+ // accessed by both the cpu and the usb bus master
+ // at the same time. The Frame List ocupies 4K bytes,
+ // and must be aligned on 4-Kbyte boundaries.
+ //
+ Bytes = 4096;
+ Pages = EFI_SIZE_TO_PAGES (Bytes);
+
+ Status = Uhc->PciIo->AllocateBuffer (
+ Uhc->PciIo,
+ AllocateAnyPages,
+ EfiBootServicesData,
+ Pages,
+ &Buffer,
+ 0
+ );
+
+ if (EFI_ERROR (Status)) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ Status = Uhc->PciIo->Map (
+ Uhc->PciIo,
+ EfiPciIoOperationBusMasterCommonBuffer,
+ Buffer,
+ &Bytes,
+ &MappedAddr,
+ &Mapping
+ );
+
+ if (EFI_ERROR (Status) || (Bytes != 4096)) {
+ Status = EFI_UNSUPPORTED;
+ goto ON_ERROR;
+ }
+
+ Uhc->FrameBase = (UINT32 *) (UINTN) Buffer;
+ Uhc->FrameMapping = Mapping;
+
+ //
+ // Tell the Host Controller where the Frame List lies,
+ // by set the Frame List Base Address Register.
+ //
+ UhciSetFrameListBaseAddr (Uhc->PciIo, (VOID *) (UINTN) MappedAddr);
+
+ //
+ // Allocate the QH used by sync interrupt/control/bulk transfer.
+ // FS ctrl/bulk queue head is set to loopback so additional BW
+ // can be reclaimed. Notice, LS don't support bulk transfer and
+ // also doesn't support BW reclamation.
+ //
+ Uhc->SyncIntQh = UhciCreateQh (Uhc, 1);
+ Uhc->CtrlQh = UhciCreateQh (Uhc, 1);
+ Uhc->BulkQh = UhciCreateQh (Uhc, 1);
+
+ if ((Uhc->SyncIntQh == NULL) || (Uhc->CtrlQh == NULL) || (Uhc->BulkQh == NULL)) {
+ Uhc->PciIo->Unmap (Uhc->PciIo, Mapping);
+ Status = EFI_OUT_OF_RESOURCES;
+ goto ON_ERROR;
+ }
+
+ //
+ // +-------------+
+ // | |
+ // Link the three together: SyncIntQh->CtrlQh->BulkQh <---------+
+ // Each frame entry is linked to this sequence of QH. These QH
+ // will remain on the schedul, never got removed
+ //
+ PhyAddr = UsbHcGetPciAddressForHostMem (Uhc->MemPool, Uhc->CtrlQh, sizeof (UHCI_QH_HW));
+ Uhc->SyncIntQh->QhHw.HorizonLink = QH_HLINK (PhyAddr, FALSE);
+ Uhc->SyncIntQh->NextQh = Uhc->CtrlQh;
+
+ PhyAddr = UsbHcGetPciAddressForHostMem (Uhc->MemPool, Uhc->BulkQh, sizeof (UHCI_QH_HW));
+ Uhc->CtrlQh->QhHw.HorizonLink = QH_HLINK (PhyAddr, FALSE);
+ Uhc->CtrlQh->NextQh = Uhc->BulkQh;
+
+ //
+ // Some old platform such as Intel's Tiger 4 has a difficult time
+ // in supporting the full speed bandwidth reclamation in the previous
+ // mentioned form. Most new platforms don't suffer it.
+ //
+ Uhc->BulkQh->QhHw.HorizonLink = QH_HLINK (PhyAddr, FALSE);
+
+ Uhc->BulkQh->NextQh = NULL;
+
+ Uhc->FrameBaseHostAddr = AllocateZeroPool (4096);
+ if (Uhc->FrameBaseHostAddr == NULL) {
+ Status = EFI_OUT_OF_RESOURCES;
+ goto ON_ERROR;
+ }
+
+ PhyAddr = UsbHcGetPciAddressForHostMem (Uhc->MemPool, Uhc->SyncIntQh, sizeof (UHCI_QH_HW));
+ for (Index = 0; Index < UHCI_FRAME_NUM; Index++) {
+ Uhc->FrameBase[Index] = QH_HLINK (PhyAddr, FALSE);
+ Uhc->FrameBaseHostAddr[Index] = (UINT32)(UINTN)Uhc->SyncIntQh;
+ }
+
+ return EFI_SUCCESS;
+
+ON_ERROR:
+ if (Uhc->SyncIntQh != NULL) {
+ UsbHcFreeMem (Uhc->MemPool, Uhc->SyncIntQh, sizeof (UHCI_QH_SW));
+ }
+
+ if (Uhc->CtrlQh != NULL) {
+ UsbHcFreeMem (Uhc->MemPool, Uhc->CtrlQh, sizeof (UHCI_QH_SW));
+ }
+
+ if (Uhc->BulkQh != NULL) {
+ UsbHcFreeMem (Uhc->MemPool, Uhc->BulkQh, sizeof (UHCI_QH_SW));
+ }
+
+ Uhc->PciIo->FreeBuffer (Uhc->PciIo, Pages, Buffer);
+ return Status;
+}
+
+
+/**
+ Destory FrameList buffer.
+
+ @param Uhc The UHCI device.
+
+**/
+VOID
+UhciDestoryFrameList (
+ IN USB_HC_DEV *Uhc
+ )
+{
+ //
+ // Unmap the common buffer for framelist entry,
+ // and free the common buffer.
+ // Uhci's frame list occupy 4k memory.
+ //
+ Uhc->PciIo->Unmap (Uhc->PciIo, Uhc->FrameMapping);
+
+ Uhc->PciIo->FreeBuffer (
+ Uhc->PciIo,
+ EFI_SIZE_TO_PAGES (4096),
+ (VOID *) Uhc->FrameBase
+ );
+
+ if (Uhc->FrameBaseHostAddr != NULL) {
+ FreePool (Uhc->FrameBaseHostAddr);
+ }
+
+ if (Uhc->SyncIntQh != NULL) {
+ UsbHcFreeMem (Uhc->MemPool, Uhc->SyncIntQh, sizeof (UHCI_QH_SW));
+ }
+
+ if (Uhc->CtrlQh != NULL) {
+ UsbHcFreeMem (Uhc->MemPool, Uhc->CtrlQh, sizeof (UHCI_QH_SW));
+ }
+
+ if (Uhc->BulkQh != NULL) {
+ UsbHcFreeMem (Uhc->MemPool, Uhc->BulkQh, sizeof (UHCI_QH_SW));
+ }
+
+ Uhc->FrameBase = NULL;
+ Uhc->FrameBaseHostAddr = NULL;
+ Uhc->SyncIntQh = NULL;
+ Uhc->CtrlQh = NULL;
+ Uhc->BulkQh = NULL;
+}
+
+
+/**
+ Convert the poll rate to the maxium 2^n that is smaller
+ than Interval.
+
+ @param Interval The poll rate to convert.
+
+ @return The converted poll rate.
+
+**/
+UINTN
+UhciConvertPollRate (
+ IN UINTN Interval
+ )
+{
+ UINTN BitCount;
+
+ ASSERT (Interval != 0);
+
+ //
+ // Find the index (1 based) of the highest non-zero bit
+ //
+ BitCount = 0;
+
+ while (Interval != 0) {
+ Interval >>= 1;
+ BitCount++;
+ }
+
+ return (UINTN)1 << (BitCount - 1);
+}
+
+
+/**
+ Link a queue head (for asynchronous interrupt transfer) to
+ the frame list.
+
+ @param Uhc The UHCI device.
+ @param Qh The queue head to link into.
+
+**/
+VOID
+UhciLinkQhToFrameList (
+ USB_HC_DEV *Uhc,
+ UHCI_QH_SW *Qh
+ )
+{
+ UINTN Index;
+ UHCI_QH_SW *Prev;
+ UHCI_QH_SW *Next;
+ EFI_PHYSICAL_ADDRESS PhyAddr;
+ EFI_PHYSICAL_ADDRESS QhPciAddr;
+
+ ASSERT ((Uhc->FrameBase != NULL) && (Qh != NULL));
+
+ QhPciAddr = UsbHcGetPciAddressForHostMem (Uhc->MemPool, Qh, sizeof (UHCI_QH_HW));
+
+ for (Index = 0; Index < UHCI_FRAME_NUM; Index += Qh->Interval) {
+ //
+ // First QH can't be NULL because we always keep static queue
+ // heads on the frame list
+ //
+ ASSERT (!LINK_TERMINATED (Uhc->FrameBase[Index]));
+ Next = (UHCI_QH_SW*)(UINTN)Uhc->FrameBaseHostAddr[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
+ //
+ // This method is very much the same as that used by EHCI.
+ // Because each QH's interval is round down to 2^n, poll
+ // rate is correct.
+ //
+ 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 FrameBase[0] and at FrameBase[0] it is
+ // impossible (Next == Qh)
+ //
+ if (Next == Qh) {
+ continue;
+ }
+
+ if (Next->Interval == Qh->Interval) {
+ //
+ // If there is a QH with the same interval, it locates at
+ // FrameBase[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_HLINK (QhPciAddr, 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;
+ PhyAddr = UsbHcGetPciAddressForHostMem (Uhc->MemPool, Next, sizeof (UHCI_QH_HW));
+ Qh->QhHw.HorizonLink = QH_HLINK (PhyAddr, FALSE);
+ }
+
+ if (Prev == NULL) {
+ Uhc->FrameBase[Index] = QH_HLINK (QhPciAddr, FALSE);
+ Uhc->FrameBaseHostAddr[Index] = (UINT32)(UINTN)Qh;
+ } else {
+ Prev->NextQh = Qh;
+ Prev->QhHw.HorizonLink = QH_HLINK (QhPciAddr, FALSE);
+ }
+ }
+}
+
+
+/**
+ Unlink QH from the frame list is easier: find all
+ the precedence node, and pointer there next to QhSw's
+ next.
+
+ @param Uhc The UHCI device.
+ @param Qh The queue head to unlink.
+
+**/
+VOID
+UhciUnlinkQhFromFrameList (
+ USB_HC_DEV *Uhc,
+ UHCI_QH_SW *Qh
+ )
+{
+ UINTN Index;
+ UHCI_QH_SW *Prev;
+ UHCI_QH_SW *This;
+
+ ASSERT ((Uhc->FrameBase != NULL) && (Qh != NULL));
+
+ for (Index = 0; Index < UHCI_FRAME_NUM; Index += Qh->Interval) {
+ //
+ // Frame link can't be NULL because we always keep static
+ // queue heads on the frame list
+ //
+ ASSERT (!LINK_TERMINATED (Uhc->FrameBase[Index]));
+ This = (UHCI_QH_SW*)(UINTN)Uhc->FrameBaseHostAddr[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.
+ //
+ if (This == NULL) {
+ continue;
+ }
+
+ if (Prev == NULL) {
+ //
+ // Qh is the first entry in the frame
+ //
+ Uhc->FrameBase[Index] = Qh->QhHw.HorizonLink;
+ Uhc->FrameBaseHostAddr[Index] = (UINT32)(UINTN)Qh->NextQh;
+ } else {
+ Prev->NextQh = Qh->NextQh;
+ Prev->QhHw.HorizonLink = Qh->QhHw.HorizonLink;
+ }
+ }
+}
+
+
+/**
+ Check TDs Results.
+
+ @param Uhc This UHCI device.
+ @param Td UHCI_TD_SW to check.
+ @param IsLow Is Low Speed Device.
+ @param QhResult Return the result of this TD list.
+
+ @return Whether the TD's result is finialized.
+
+**/
+BOOLEAN
+UhciCheckTdStatus (
+ IN USB_HC_DEV *Uhc,
+ IN UHCI_TD_SW *Td,
+ IN BOOLEAN IsLow,
+ OUT UHCI_QH_RESULT *QhResult
+ )
+{
+ UINTN Len;
+ UINT8 State;
+ UHCI_TD_HW *TdHw;
+ BOOLEAN Finished;
+
+ Finished = TRUE;
+
+ //
+ // Initialize the data toggle to that of the first
+ // TD. The next toggle to use is either:
+ // 1. first TD's toggle if no TD is executed OK
+ // 2. the next toggle of last executed-OK TD
+ //
+ QhResult->Result = EFI_USB_NOERROR;
+ QhResult->NextToggle = (UINT8)Td->TdHw.DataToggle;
+ QhResult->Complete = 0;
+
+ while (Td != NULL) {
+ TdHw = &Td->TdHw;
+ State = (UINT8)TdHw->Status;
+
+ //
+ // UHCI will set STALLED bit when it abort the execution
+ // of TD list. There are several reasons:
+ // 1. BABBLE error happened
+ // 2. Received a STALL response
+ // 3. Error count decreased to zero.
+ //
+ // It also set CRC/Timeout/NAK/Buffer Error/BitStuff Error
+ // bits when corresponding conditions happen. But these
+ // conditions are not deadly, that is a TD can successfully
+ // completes even these bits are set. But it is likely that
+ // upper layer won't distinguish these condtions. So, only
+ // set these bits when TD is actually halted.
+ //
+ if ((State & USBTD_STALLED) != 0) {
+ if ((State & USBTD_BABBLE) != 0) {
+ QhResult->Result |= EFI_USB_ERR_BABBLE;
+
+ } else if (TdHw->ErrorCount != 0) {
+ QhResult->Result |= EFI_USB_ERR_STALL;
+ }
+
+ if ((State & USBTD_CRC) != 0) {
+ QhResult->Result |= EFI_USB_ERR_CRC;
+ }
+
+ if ((State & USBTD_BUFFERR) != 0) {
+ QhResult->Result |= EFI_USB_ERR_BUFFER;
+ }
+
+ if ((Td->TdHw.Status & USBTD_BITSTUFF) != 0) {
+ QhResult->Result |= EFI_USB_ERR_BITSTUFF;
+ }
+
+ if (TdHw->ErrorCount == 0) {
+ QhResult->Result |= EFI_USB_ERR_TIMEOUT;
+ }
+
+ Finished = TRUE;
+ goto ON_EXIT;
+
+ } else if ((State & USBTD_ACTIVE) != 0) {
+ //
+ // The TD is still active, no need to check further.
+ //
+ QhResult->Result |= EFI_USB_ERR_NOTEXECUTE;
+
+ Finished = FALSE;
+ goto ON_EXIT;
+
+ } else {
+ //
+ // Update the next data toggle, it is always the
+ // next to the last known-good TD's data toggle if
+ // any TD is executed OK
+ //
+ QhResult->NextToggle = (UINT8) (1 - (UINT8)TdHw->DataToggle);
+
+ //
+ // This TD is finished OK or met short packet read. Update the
+ // transfer length if it isn't a SETUP.
+ //
+ Len = (TdHw->ActualLen + 1) & 0x7FF;
+
+ if (TdHw->PidCode != SETUP_PACKET_ID) {
+ QhResult->Complete += Len;
+ }
+
+ //
+ // Short packet condition for full speed input TD, also
+ // terminate the transfer
+ //
+ if (!IsLow && (TdHw->ShortPacket == 1) && (Len < Td->DataLen)) {
+ DEBUG ((DEBUG_VERBOSE, "UhciCheckTdStatus: short packet read occurred\n"));
+
+ Finished = TRUE;
+ goto ON_EXIT;
+ }
+ }
+
+ Td = Td->NextTd;
+ }
+
+ON_EXIT:
+ //
+ // Check whether HC is halted. Don't move this up. It must be
+ // called after data toggle is successfully updated.
+ //
+ if (!UhciIsHcWorking (Uhc->PciIo)) {
+ QhResult->Result |= EFI_USB_ERR_SYSTEM;
+ Finished = TRUE;
+ }
+
+ if (Finished) {
+ Uhc->PciIo->Flush (Uhc->PciIo);
+ }
+
+ UhciAckAllInterrupt (Uhc);
+ return Finished;
+}
+
+
+/**
+ Check the result of the transfer.
+
+ @param Uhc The UHCI device.
+ @param Qh The queue head of the transfer.
+ @param Td The first TDs of the transfer.
+ @param TimeOut TimeOut value in milliseconds.
+ @param IsLow Is Low Speed Device.
+ @param QhResult The variable to return result.
+
+ @retval EFI_SUCCESS The transfer finished with success.
+ @retval EFI_DEVICE_ERROR Transfer failed.
+
+**/
+EFI_STATUS
+UhciExecuteTransfer (
+ IN USB_HC_DEV *Uhc,
+ IN UHCI_QH_SW *Qh,
+ IN UHCI_TD_SW *Td,
+ IN UINTN TimeOut,
+ IN BOOLEAN IsLow,
+ OUT UHCI_QH_RESULT *QhResult
+ )
+{
+ UINTN Index;
+ UINTN Delay;
+ BOOLEAN Finished;
+ EFI_STATUS Status;
+ BOOLEAN InfiniteLoop;
+
+ Finished = FALSE;
+ Status = EFI_SUCCESS;
+ Delay = TimeOut * UHC_1_MILLISECOND;
+ InfiniteLoop = FALSE;
+
+ //
+ // According to UEFI spec section 16.2.4, 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 < Delay); Index++) {
+ Finished = UhciCheckTdStatus (Uhc, Td, IsLow, QhResult);
+
+ //
+ // Transfer is OK or some error occurred (TD inactive)
+ //
+ if (Finished) {
+ break;
+ }
+
+ gBS->Stall (UHC_1_MICROSECOND);
+ }
+
+ if (!Finished) {
+ DEBUG ((EFI_D_ERROR, "UhciExecuteTransfer: execution not finished for %dms\n", (UINT32)TimeOut));
+ UhciDumpQh (Qh);
+ UhciDumpTds (Td);
+
+ Status = EFI_TIMEOUT;
+
+ } else if (QhResult->Result != EFI_USB_NOERROR) {
+ DEBUG ((EFI_D_ERROR, "UhciExecuteTransfer: execution failed with result %x\n", QhResult->Result));
+ UhciDumpQh (Qh);
+ UhciDumpTds (Td);
+
+ Status = EFI_DEVICE_ERROR;
+ }
+
+ return Status;
+}
+
+
+/**
+ Update Async Request, QH and TDs.
+
+ @param Uhc The UHCI device.
+ @param AsyncReq The UHCI asynchronous transfer to update.
+ @param Result Transfer reslut.
+ @param NextToggle The toggle of next data.
+
+**/
+VOID
+UhciUpdateAsyncReq (
+ IN USB_HC_DEV *Uhc,
+ IN UHCI_ASYNC_REQUEST *AsyncReq,
+ IN UINT32 Result,
+ IN UINT32 NextToggle
+ )
+{
+ UHCI_QH_SW *Qh;
+ UHCI_TD_SW *FirstTd;
+ UHCI_TD_SW *Td;
+
+ Qh = AsyncReq->QhSw;
+ FirstTd = AsyncReq->FirstTd;
+
+ if (Result == EFI_USB_NOERROR) {
+ //
+ // The last transfer succeeds. Then we need to update
+ // the Qh and Td for next round of transfer.
+ // 1. Update the TD's data toggle
+ // 2. Activate all the TDs
+ // 3. Link the TD to the queue head again since during
+ // execution, queue head's TD pointer is changed by
+ // hardware.
+ //
+ for (Td = FirstTd; Td != NULL; Td = Td->NextTd) {
+ Td->TdHw.DataToggle = NextToggle;
+ NextToggle ^= 1;
+ Td->TdHw.Status |= USBTD_ACTIVE;
+ }
+
+ UhciLinkTdToQh (Uhc, Qh, FirstTd);
+ return ;
+ }
+}
+
+
+/**
+ Create Async Request node, and Link to List.
+
+ @param Uhc The UHCI device.
+ @param Qh The queue head of the transfer.
+ @param FirstTd First TD of the transfer.
+ @param DevAddr Device Address.
+ @param EndPoint EndPoint Address.
+ @param DataLen Data length.
+ @param Interval Polling Interval when inserted to frame list.
+ @param Data Data buffer, unmapped.
+ @param Callback Callback after interrupt transfeer.
+ @param Context Callback Context passed as function parameter.
+ @param IsLow Is Low Speed.
+
+ @retval EFI_SUCCESS An asynchronous transfer is created.
+ @retval EFI_INVALID_PARAMETER Paremeter is error.
+ @retval EFI_OUT_OF_RESOURCES Failed because of resource shortage.
+
+**/
+EFI_STATUS
+UhciCreateAsyncReq (
+ IN USB_HC_DEV *Uhc,
+ IN UHCI_QH_SW *Qh,
+ IN UHCI_TD_SW *FirstTd,
+ IN UINT8 DevAddr,
+ IN UINT8 EndPoint,
+ IN UINTN DataLen,
+ IN UINTN Interval,
+ IN UINT8 *Data,
+ IN EFI_ASYNC_USB_TRANSFER_CALLBACK Callback,
+ IN VOID *Context,
+ IN BOOLEAN IsLow
+ )
+{
+ UHCI_ASYNC_REQUEST *AsyncReq;
+
+ AsyncReq = AllocatePool (sizeof (UHCI_ASYNC_REQUEST));
+
+ if (AsyncReq == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ //
+ // Fill Request field. Data is allocated host memory, not mapped
+ //
+ AsyncReq->Signature = UHCI_ASYNC_INT_SIGNATURE;
+ AsyncReq->DevAddr = DevAddr;
+ AsyncReq->EndPoint = EndPoint;
+ AsyncReq->DataLen = DataLen;
+ AsyncReq->Interval = UhciConvertPollRate(Interval);
+ AsyncReq->Data = Data;
+ AsyncReq->Callback = Callback;
+ AsyncReq->Context = Context;
+ AsyncReq->QhSw = Qh;
+ AsyncReq->FirstTd = FirstTd;
+ AsyncReq->IsLow = IsLow;
+
+ //
+ // Insert the new interrupt transfer to the head of the list.
+ // The interrupt transfer's monitor function scans the whole
+ // list from head to tail. The new interrupt transfer MUST be
+ // added to the head of the list.
+ //
+ InsertHeadList (&(Uhc->AsyncIntList), &(AsyncReq->Link));
+
+ return EFI_SUCCESS;
+}
+
+
+/**
+ Free an asynchronous request's resource such as memory.
+
+ @param Uhc The UHCI device.
+ @param AsyncReq The asynchronous request to free.
+
+**/
+VOID
+UhciFreeAsyncReq (
+ IN USB_HC_DEV *Uhc,
+ IN UHCI_ASYNC_REQUEST *AsyncReq
+ )
+{
+ ASSERT ((Uhc != NULL) && (AsyncReq != NULL));
+
+ UhciDestoryTds (Uhc, AsyncReq->FirstTd);
+ UsbHcFreeMem (Uhc->MemPool, AsyncReq->QhSw, sizeof (UHCI_QH_SW));
+
+ if (AsyncReq->Data != NULL) {
+ UsbHcFreeMem (Uhc->MemPool, AsyncReq->Data, AsyncReq->DataLen);
+ }
+
+ gBS->FreePool (AsyncReq);
+}
+
+
+/**
+ Unlink an asynchronous request's from UHC's asynchronus list.
+ also remove the queue head from the frame list. If FreeNow,
+ release its resource also. Otherwise, add the request to the
+ UHC's recycle list to wait for a while before release the memory.
+ Until then, hardware won't hold point to the request.
+
+ @param Uhc The UHCI device.
+ @param AsyncReq The asynchronous request to free.
+ @param FreeNow If TRUE, free the resource immediately, otherwise
+ add the request to recycle wait list.
+
+**/
+VOID
+UhciUnlinkAsyncReq (
+ IN USB_HC_DEV *Uhc,
+ IN UHCI_ASYNC_REQUEST *AsyncReq,
+ IN BOOLEAN FreeNow
+ )
+{
+ ASSERT ((Uhc != NULL) && (AsyncReq != NULL));
+
+ RemoveEntryList (&(AsyncReq->Link));
+ UhciUnlinkQhFromFrameList (Uhc, AsyncReq->QhSw);
+
+ if (FreeNow) {
+ UhciFreeAsyncReq (Uhc, AsyncReq);
+ } else {
+ //
+ // To sychronize with hardware, mark the queue head as inactive
+ // then add AsyncReq to UHC's recycle list
+ //
+ AsyncReq->QhSw->QhHw.VerticalLink = QH_VLINK (NULL, TRUE);
+ AsyncReq->Recycle = Uhc->RecycleWait;
+ Uhc->RecycleWait = AsyncReq;
+ }
+}
+
+
+/**
+ Delete Async Interrupt QH and TDs.
+
+ @param Uhc The UHCI device.
+ @param DevAddr Device Address.
+ @param EndPoint EndPoint Address.
+ @param Toggle The next data toggle to use.
+
+ @retval EFI_SUCCESS The request is deleted.
+ @retval EFI_INVALID_PARAMETER Paremeter is error.
+ @retval EFI_NOT_FOUND The asynchronous isn't found.
+
+**/
+EFI_STATUS
+UhciRemoveAsyncReq (
+ IN USB_HC_DEV *Uhc,
+ IN UINT8 DevAddr,
+ IN UINT8 EndPoint,
+ OUT UINT8 *Toggle
+ )
+{
+ EFI_STATUS Status;
+ UHCI_ASYNC_REQUEST *AsyncReq;
+ UHCI_QH_RESULT QhResult;
+ LIST_ENTRY *Link;
+ BOOLEAN Found;
+
+ Status = EFI_SUCCESS;
+
+ //
+ // If no asynchronous interrupt transaction exists
+ //
+ if (IsListEmpty (&(Uhc->AsyncIntList))) {
+ return EFI_SUCCESS;
+ }
+
+ //
+ // Find the asynchronous transfer to this device/endpoint pair
+ //
+ Found = FALSE;
+ Link = Uhc->AsyncIntList.ForwardLink;
+
+ do {
+ AsyncReq = UHCI_ASYNC_INT_FROM_LINK (Link);
+ Link = Link->ForwardLink;
+
+ if ((AsyncReq->DevAddr == DevAddr) && (AsyncReq->EndPoint == EndPoint)) {
+ Found = TRUE;
+ break;
+ }
+
+ } while (Link != &(Uhc->AsyncIntList));
+
+ if (!Found) {
+ return EFI_NOT_FOUND;
+ }
+
+ //
+ // Check the result of the async transfer then update it
+ // to get the next data toggle to use.
+ //
+ UhciCheckTdStatus (Uhc, AsyncReq->FirstTd, AsyncReq->IsLow, &QhResult);
+ *Toggle = QhResult.NextToggle;
+
+ //
+ // Don't release the request now, keep it to synchronize with hardware.
+ //
+ UhciUnlinkAsyncReq (Uhc, AsyncReq, FALSE);
+ return Status;
+}
+
+
+/**
+ Recycle the asynchronouse request. When a queue head
+ is unlinked from frame list, host controller hardware
+ may still hold a cached pointer to it. To synchronize
+ with hardware, the request is released in two steps:
+ first it is linked to the UHC's RecycleWait list. At
+ the next time UhciMonitorAsyncReqList is fired, it is
+ moved to UHC's Recylelist. Then, at another timer
+ activation, all the requests on Recycle list is freed.
+ This guarrantes that each unlink queue head keeps
+ existing for at least 50ms, far enough for the hardware
+ to clear its cache.
+
+ @param Uhc The UHCI device.
+
+**/
+VOID
+UhciRecycleAsyncReq (
+ IN USB_HC_DEV *Uhc
+ )
+{
+ UHCI_ASYNC_REQUEST *Req;
+ UHCI_ASYNC_REQUEST *Next;
+
+ Req = Uhc->Recycle;
+
+ while (Req != NULL) {
+ Next = Req->Recycle;
+ UhciFreeAsyncReq (Uhc, Req);
+ Req = Next;
+ }
+
+ Uhc->Recycle = Uhc->RecycleWait;
+ Uhc->RecycleWait = NULL;
+}
+
+
+
+/**
+ Release all the asynchronous transfers on the lsit.
+
+ @param Uhc The UHCI device.
+
+**/
+VOID
+UhciFreeAllAsyncReq (
+ IN USB_HC_DEV *Uhc
+ )
+{
+ LIST_ENTRY *Head;
+ UHCI_ASYNC_REQUEST *AsyncReq;
+
+ //
+ // Call UhciRecycleAsyncReq twice. The requests on Recycle
+ // will be released at the first call; The requests on
+ // RecycleWait will be released at the second call.
+ //
+ UhciRecycleAsyncReq (Uhc);
+ UhciRecycleAsyncReq (Uhc);
+
+ Head = &(Uhc->AsyncIntList);
+
+ if (IsListEmpty (Head)) {
+ return;
+ }
+
+ while (!IsListEmpty (Head)) {
+ AsyncReq = UHCI_ASYNC_INT_FROM_LINK (Head->ForwardLink);
+ UhciUnlinkAsyncReq (Uhc, AsyncReq, TRUE);
+ }
+}
+
+
+/**
+ Interrupt transfer periodic check handler.
+
+ @param Event The event of the time.
+ @param Context Context of the event, pointer to USB_HC_DEV.
+
+**/
+VOID
+EFIAPI
+UhciMonitorAsyncReqList (
+ IN EFI_EVENT Event,
+ IN VOID *Context
+ )
+{
+ UHCI_ASYNC_REQUEST *AsyncReq;
+ LIST_ENTRY *Link;
+ USB_HC_DEV *Uhc;
+ VOID *Data;
+ BOOLEAN Finished;
+ UHCI_QH_RESULT QhResult;
+
+ Uhc = (USB_HC_DEV *) Context;
+
+ //
+ // Recycle the asynchronous requests expired, and promote
+ // requests waiting to be recycled the next time when this
+ // timer expires
+ //
+ UhciRecycleAsyncReq (Uhc);
+
+ if (IsListEmpty (&(Uhc->AsyncIntList))) {
+ return ;
+ }
+
+ //
+ // This loop must be delete safe
+ //
+ Link = Uhc->AsyncIntList.ForwardLink;
+
+ do {
+ AsyncReq = UHCI_ASYNC_INT_FROM_LINK (Link);
+ Link = Link->ForwardLink;
+
+ Finished = UhciCheckTdStatus (Uhc, AsyncReq->FirstTd, AsyncReq->IsLow, &QhResult);
+
+ if (!Finished) {
+ continue;
+ }
+
+ //
+ // Copy the data to temporary buffer if there are some
+ // data transferred. We may have zero-length packet.
+ // Make sure the data received from HW is no more than expected.
+ //
+ Data = NULL;
+
+ if ((QhResult.Complete != 0) && (QhResult.Complete <= AsyncReq->DataLen)) {
+ Data = AllocatePool (QhResult.Complete);
+
+ if (Data == NULL) {
+ return ;
+ }
+
+ CopyMem (Data, AsyncReq->FirstTd->Data, QhResult.Complete);
+ }
+
+ UhciUpdateAsyncReq (Uhc, AsyncReq, QhResult.Result, QhResult.NextToggle);
+
+ //
+ // Now, either transfer is SUCCESS or met errors since
+ // we have skipped to next transfer earlier if current
+ // transfer is still active.
+ //
+ if (QhResult.Result == EFI_USB_NOERROR) {
+ AsyncReq->Callback (Data, QhResult.Complete, AsyncReq->Context, QhResult.Result);
+ } else {
+ //
+ // Leave error recovery to its related device driver.
+ // A common case of the error recovery is to re-submit
+ // the interrupt transfer. When an interrupt transfer
+ // is re-submitted, its position in the linked list is
+ // changed. It is inserted to the head of the linked
+ // list, while this function scans the whole list from
+ // head to tail. Thus, the re-submitted interrupt transfer's
+ // callback function will not be called again in this round.
+ //
+ AsyncReq->Callback (NULL, 0, AsyncReq->Context, QhResult.Result);
+ }
+
+ if (Data != NULL) {
+ gBS->FreePool (Data);
+ }
+ } while (Link != &(Uhc->AsyncIntList));
+}