summaryrefslogtreecommitdiff
path: root/SamsungPlatformPkg/ExynosPkg/Exynos5250/Drivers/OhciDxe/OhciSched.c
diff options
context:
space:
mode:
authorSteven Kinney <steven.kinney@linaro.org>2013-10-02 16:33:44 -0500
committerSteven Kinney <steven.kinney@linaro.org>2013-10-02 16:33:44 -0500
commit84502da15069f664cc729da8fdc53bfa832234eb (patch)
tree3a0392c05f26c3167c1381be32af424a3aca0ebd /SamsungPlatformPkg/ExynosPkg/Exynos5250/Drivers/OhciDxe/OhciSched.c
parent09e7ec163baf29e6ee85e36c9ef348ebb9369b7e (diff)
parenteafdd4f792145f3470b66e66fdb1d6da2720820b (diff)
Merge branch 'linaro-topic-arndale' into x-integ
Diffstat (limited to 'SamsungPlatformPkg/ExynosPkg/Exynos5250/Drivers/OhciDxe/OhciSched.c')
-rw-r--r--SamsungPlatformPkg/ExynosPkg/Exynos5250/Drivers/OhciDxe/OhciSched.c1254
1 files changed, 1254 insertions, 0 deletions
diff --git a/SamsungPlatformPkg/ExynosPkg/Exynos5250/Drivers/OhciDxe/OhciSched.c b/SamsungPlatformPkg/ExynosPkg/Exynos5250/Drivers/OhciDxe/OhciSched.c
new file mode 100644
index 000000000..a8f99a3d4
--- /dev/null
+++ b/SamsungPlatformPkg/ExynosPkg/Exynos5250/Drivers/OhciDxe/OhciSched.c
@@ -0,0 +1,1254 @@
+/** @file
+
+ The EHCI register operation routines.
+
+Copyright (c) 2007 - 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 "Ohci.h"
+
+
+/**
+ Create Frame List Structure.
+
+ @param Ohc OHCI device.
+
+ @retval EFI_OUT_OF_RESOURCES Can't allocate memory resources.
+ @retval EFI_UNSUPPORTED Map memory fail.
+ @retval EFI_SUCCESS Success.
+
+**/
+EFI_STATUS
+OhciInitFrameList (
+ IN USB_HC_DEV *Ohc
+ )
+{
+ EFI_PHYSICAL_ADDRESS MappedAddr;
+ EFI_STATUS Status;
+ VOID *Buffer;
+ VOID *Mapping;
+ UINTN Pages;
+ UINTN Bytes;
+ UINTN Index;
+ EFI_PHYSICAL_ADDRESS PhyAddr;
+
+ DEBUG((EFI_D_INIT, "+++OhciInitFrameList()\n"));
+
+ //
+ // 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 = Ohc->PciIo->AllocateBuffer (
+ Ohc->PciIo,
+ AllocateAnyPages,
+ EfiBootServicesData,
+ Pages,
+ &Buffer,
+ 0
+ );
+
+ if (EFI_ERROR (Status)) {
+ DEBUG((EFI_D_ERROR, "FAIL! AllocateBuffer\n"));
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ Status = Ohc->PciIo->Map (
+ Ohc->PciIo,
+ EfiPciIoOperationBusMasterCommonBuffer,
+ Buffer,
+ &Bytes,
+ &MappedAddr,
+ &Mapping
+ );
+
+#if 1
+ if (EFI_ERROR (Status) || (Bytes != 4096)) {
+ DEBUG((EFI_D_ERROR, "FAIL! Map\n"));
+ Status = EFI_UNSUPPORTED;
+ return Status;
+ }
+
+ Ohc->Hcca = (UINT32 *) (UINTN) Buffer;
+ Ohc->HccaMapping = Mapping;
+
+ OhciWriteReg(Ohc, HC_HCCA_OFFSET, (UINT32)Buffer);
+
+ return EFI_SUCCESS;
+
+#else
+ if (EFI_ERROR (Status) || (Bytes != 4096)) {
+ DEBUG((EFI_D_ERROR, "FAIL! Map\n"));
+ Status = EFI_UNSUPPORTED;
+ goto ON_ERROR;
+ }
+
+
+ Ohc->FrameBase = (UINT32 *) (UINTN) Buffer;
+ Ohc->FrameMapping = Mapping;
+
+ //
+ // Tell the Host Controller where the Frame List lies,
+ // by set the Frame List Base Address Register.
+ //
+ //OhciSetFrameListBaseAddr (Ohc->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.
+ //
+ Ohc->SyncIntQh = OhciCreateQh (Ohc, 1);
+ Ohc->CtrlQh = OhciCreateQh (Ohc, 1);
+ Ohc->BulkQh = OhciCreateQh (Ohc, 1);
+
+ if ((Ohc->SyncIntQh == NULL) || (Ohc->CtrlQh == NULL) || (Ohc->BulkQh == NULL)) {
+ Ohc->PciIo->Unmap (Ohc->PciIo, Mapping);
+ DEBUG((EFI_D_ERROR, "FAIL! NULL1\n"));
+ 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 (Ohc->MemPool, Ohc->CtrlQh, sizeof (OHCI_QH_HW));
+ Ohc->SyncIntQh->QhHw.HorizonLink = QH_HLINK (PhyAddr, FALSE);
+ Ohc->SyncIntQh->NextQh = Ohc->CtrlQh;
+
+ PhyAddr = UsbHcGetPciAddressForHostMem (Ohc->MemPool, Ohc->BulkQh, sizeof (OHCI_QH_HW));
+ Ohc->CtrlQh->QhHw.HorizonLink = QH_HLINK (PhyAddr, FALSE);
+ Ohc->CtrlQh->NextQh = Ohc->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.
+ //
+ Ohc->BulkQh->QhHw.HorizonLink = QH_HLINK (PhyAddr, FALSE);
+
+ Ohc->BulkQh->NextQh = NULL;
+
+ Ohc->FrameBaseHostAddr = AllocateZeroPool (4096);
+ if (Ohc->FrameBaseHostAddr == NULL) {
+ DEBUG((EFI_D_ERROR, "FAIL! NULL2\n"));
+ Status = EFI_OUT_OF_RESOURCES;
+ goto ON_ERROR;
+ }
+
+ PhyAddr = UsbHcGetPciAddressForHostMem (Ohc->MemPool, Ohc->SyncIntQh, sizeof (OHCI_QH_HW));
+ for (Index = 0; Index < OHCI_FRAME_NUM; Index++) {
+ Ohc->FrameBase[Index] = QH_HLINK (PhyAddr, FALSE);
+ Ohc->FrameBaseHostAddr[Index] = (UINT32)(UINTN)Ohc->SyncIntQh;
+ }
+
+ DEBUG((EFI_D_INIT, "---OhciInitFrameList()\n"));
+
+ return EFI_SUCCESS;
+
+ON_ERROR:
+ if (Ohc->SyncIntQh != NULL) {
+ UsbHcFreeMem (Ohc->MemPool, Ohc->SyncIntQh, sizeof (OHCI_QH_SW));
+ }
+
+ if (Ohc->CtrlQh != NULL) {
+ UsbHcFreeMem (Ohc->MemPool, Ohc->CtrlQh, sizeof (OHCI_QH_SW));
+ }
+
+ if (Ohc->BulkQh != NULL) {
+ UsbHcFreeMem (Ohc->MemPool, Ohc->BulkQh, sizeof (OHCI_QH_SW));
+ }
+
+ Ohc->PciIo->FreeBuffer (Ohc->PciIo, Pages, Buffer);
+ return Status;
+#endif
+}
+
+
+/**
+ Destory FrameList buffer.
+
+ @param Ohc The OHCI device.
+
+**/
+VOID
+OhciDestoryFrameList (
+ IN USB_HC_DEV *Ohc
+ )
+{
+ //
+ // Unmap the common buffer for framelist entry,
+ // and free the common buffer.
+ // Ohci's frame list occupy 4k memory.
+ //
+ DEBUG((EFI_D_INIT, "+++OhciDestoryFrameList()\n"));
+
+#if 1
+#else
+ Ohc->PciIo->Unmap (Ohc->PciIo, Ohc->FrameMapping);
+
+ Ohc->PciIo->FreeBuffer (
+ Ohc->PciIo,
+ EFI_SIZE_TO_PAGES (4096),
+ (VOID *) Ohc->FrameBase
+ );
+
+ if (Ohc->FrameBaseHostAddr != NULL) {
+ FreePool (Ohc->FrameBaseHostAddr);
+ }
+
+ if (Ohc->SyncIntQh != NULL) {
+ UsbHcFreeMem (Ohc->MemPool, Ohc->SyncIntQh, sizeof (OHCI_QH_SW));
+ }
+
+ if (Ohc->CtrlQh != NULL) {
+ UsbHcFreeMem (Ohc->MemPool, Ohc->CtrlQh, sizeof (OHCI_QH_SW));
+ }
+
+ if (Ohc->BulkQh != NULL) {
+ UsbHcFreeMem (Ohc->MemPool, Ohc->BulkQh, sizeof (OHCI_QH_SW));
+ }
+
+ Ohc->FrameBase = NULL;
+ Ohc->FrameBaseHostAddr = NULL;
+ Ohc->SyncIntQh = NULL;
+ Ohc->CtrlQh = NULL;
+ Ohc->BulkQh = NULL;
+#endif
+ DEBUG((EFI_D_INIT, "---OhciDestoryFrameList()\n"));
+}
+
+
+/**
+ 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
+OhciConvertPollRate (
+ 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 Ohc The OHCI device.
+ @param Qh The queue head to link into.
+
+**/
+VOID
+OhciLinkQhToFrameList (
+ USB_HC_DEV *Ohc,
+ OHCI_QH_SW *Qh
+ )
+{
+ UINTN Index;
+ OHCI_QH_SW *Prev;
+ OHCI_QH_SW *Next;
+ EFI_PHYSICAL_ADDRESS PhyAddr;
+ EFI_PHYSICAL_ADDRESS QhPciAddr;
+
+#if 1
+#else
+ ASSERT ((Ohc->FrameBase != NULL) && (Qh != NULL));
+
+ QhPciAddr = UsbHcGetPciAddressForHostMem (Ohc->MemPool, Qh, sizeof (OHCI_QH_HW));
+
+ for (Index = 0; Index < OHCI_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 (Ohc->FrameBase[Index]));
+ Next = (OHCI_QH_SW*)(UINTN)Ohc->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 (Ohc->MemPool, Next, sizeof (OHCI_QH_HW));
+ Qh->QhHw.HorizonLink = QH_HLINK (PhyAddr, FALSE);
+ }
+
+ if (Prev == NULL) {
+ Ohc->FrameBase[Index] = QH_HLINK (QhPciAddr, FALSE);
+ Ohc->FrameBaseHostAddr[Index] = (UINT32)(UINTN)Qh;
+ } else {
+ Prev->NextQh = Qh;
+ Prev->QhHw.HorizonLink = QH_HLINK (QhPciAddr, FALSE);
+ }
+ }
+#endif
+}
+
+
+/**
+ Unlink QH from the frame list is easier: find all
+ the precedence node, and pointer there next to QhSw's
+ next.
+
+ @param Ohc The OHCI device.
+ @param Qh The queue head to unlink.
+
+**/
+VOID
+OhciUnlinkQhFromFrameList (
+ USB_HC_DEV *Ohc,
+ OHCI_QH_SW *Qh
+ )
+{
+ UINTN Index;
+ OHCI_QH_SW *Prev;
+ OHCI_QH_SW *This;
+#if 1
+#else
+ ASSERT ((Ohc->FrameBase != NULL) && (Qh != NULL));
+
+ for (Index = 0; Index < OHCI_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 (Ohc->FrameBase[Index]));
+ This = (OHCI_QH_SW*)(UINTN)Ohc->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
+ //
+ Ohc->FrameBase[Index] = Qh->QhHw.HorizonLink;
+ Ohc->FrameBaseHostAddr[Index] = (UINT32)(UINTN)Qh->NextQh;
+ } else {
+ Prev->NextQh = Qh->NextQh;
+ Prev->QhHw.HorizonLink = Qh->QhHw.HorizonLink;
+ }
+ }
+#endif
+}
+
+
+/**
+ Check TDs Results.
+
+ @param Ohc This OHCI device.
+ @param Td OHCI_TD_HW 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
+OhciCheckTdStatus (
+ IN USB_HC_DEV *Ohc,
+ IN OHCI_ED_HW *Ed,
+ IN BOOLEAN IsLow,
+ OUT OHCI_QH_RESULT *QhResult,
+ IN UINT8 Class
+ )
+{
+ UINTN Len;
+ UINT8 State;
+ BOOLEAN Finished;
+ OHCI_TD_SW *CurTdSw;
+ OHCI_TD_HW *TdHw;
+
+ Finished = TRUE;
+
+ if(Class == HC_CLASS_CONTROL)
+ CurTdSw = Ohc->CtrlQh;
+ else // HC_CLASS_INTERRUPT
+ CurTdSw = Ohc->IntrQh;
+
+ TdHw = CurTdSw->TdHw;
+ //
+ // 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)(TdHw->gtd_info.b.data_toggle & 1);
+ QhResult->Complete = 0;
+
+#if 1
+ if(Class == HC_CLASS_CONTROL)
+ {
+ while(Ohc->Hcca->done_head != Ohc->LastTd)
+ {
+ DEBUG((EFI_D_ERROR, ".\n"));
+ OhciWriteReg(Ohc, HC_INT_STATUS_OFFSET, 0x2);
+ }
+ }
+ else
+ {
+ OhciWriteReg(Ohc, HC_INT_STATUS_OFFSET, 0x2);
+ if(Ohc->Hcca->done_head != Ohc->LastTd)
+ {
+ Finished = FALSE;
+ QhResult->Complete = 0;
+ return Finished;
+ }
+ }
+
+ Ohc->Hcca->done_head = NULL;
+ DEBUG((EFI_D_ERROR, ">\n"));
+
+ while (TdHw != NULL) {
+ if(TdHw->current_buf_ptr == NULL)
+ {
+ if(TdHw->gtd_info.b.error_count != 0)
+ {
+ QhResult->Result |= EFI_USB_ERR_NOTEXECUTE;
+ DEBUG((EFI_D_ERROR, "FAIL! EXECUTE ERROR1\n"));
+ Finished = TRUE;
+ OhciDumpSWTds(Ohc->CtrlQh);
+ OhcDumpRegs(Ohc);
+ goto ON_EXIT;
+ }
+
+ if(TdHw->gtd_info.b.condition_code != 0)
+ {
+ switch(TdHw->gtd_info.b.condition_code)
+ {
+ case 4: //stall
+ QhResult->Result |= EFI_USB_ERR_STALL;
+ DEBUG((EFI_D_ERROR, "FAIL! EFI_USB_ERR_STALL\n"));
+ break;
+ default :
+ QhResult->Result |= EFI_USB_ERR_NOTEXECUTE;
+ DEBUG((EFI_D_ERROR, "FAIL! EXECUTE ERROR2\n"));
+ break;
+ }
+
+ Finished = TRUE;
+ OhciDumpSWTds(Ohc->CtrlQh);
+ OhcDumpRegs(Ohc);
+ goto ON_EXIT;
+ }
+
+ QhResult->NextToggle = (TdHw->gtd_info.b.data_toggle & 1) ? 0 : 1;
+ //
+ // This TD is finished OK or met short packet read. Update the
+ // transfer length if it isn't a SETUP.
+ //
+
+ if (TdHw->gtd_info.b.pid != SETUP_PACKET_ID) {
+ QhResult->Complete += CurTdSw->DataLen;
+ }
+
+ //
+ // Short packet condition for full speed input TD, also
+ // terminate the transfer
+ //
+ /*
+ if (!IsLow && (TdHw->ShortPacket == 1) && (Len < Td->DataLen)) {
+ DEBUG ((EFI_D_INFO, "OhciCheckTdStatus: short packet read occured\n"));
+
+ Finished = TRUE;
+ goto ON_EXIT;
+ }*/
+ } // if(TdHw->current_buf_ptr == NULL)
+ /*else
+ {
+ QhResult->Result |= EFI_USB_ERR_NOTEXECUTE;
+ DEBUG((EFI_D_ERROR, "FAIL! EXECUTE ERROR3\n"));
+ Finished = TRUE;
+ goto ON_EXIT;
+ }*/
+ CurTdSw = CurTdSw->NextTd;
+ TdHw = CurTdSw->TdHw;
+ }
+
+ if(!Finished)
+ {
+ OhciDumpSWTds(Ohc->CtrlQh);
+ OhcDumpRegs(Ohc);
+ }
+#else
+ while (Td != NULL) {
+ TdHw = &Td->TdHw;
+ State = (UINT8)TdHw->Status;
+
+ //
+ // OHCI 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 ((EFI_D_INFO, "OhciCheckTdStatus: short packet read occured\n"));
+
+ Finished = TRUE;
+ goto ON_EXIT;
+ }
+ }
+
+ Td = Td->NextTd;
+ }
+#endif
+
+ON_EXIT:
+ //
+ // Check whether HC is halted. Don't move this up. It must be
+ // called after data toggle is successfully updated.
+ //
+ if (!OhciIsHcWorking (Ohc->PciIo)) {
+ QhResult->Result |= EFI_USB_ERR_SYSTEM;
+ Finished = TRUE;
+ }
+
+ if (Finished) {
+ Ohc->PciIo->Flush (Ohc->PciIo);
+ }
+
+ OhciAckAllInterrupt (Ohc);
+ return Finished;
+}
+
+
+/**
+ Check the result of the transfer.
+
+ @param Ohc The OHCI 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
+OhciExecuteTransfer (
+ IN USB_HC_DEV *Ohc,
+ IN OHCI_ED_HW *Ed,
+ IN UINTN TimeOut,
+ IN BOOLEAN IsLow,
+ OUT OHCI_QH_RESULT *QhResult
+ )
+{
+ UINTN Index;
+ UINTN Delay;
+ BOOLEAN Finished;
+ EFI_STATUS Status;
+
+ DEBUG((EFI_D_INIT, "+++OhciExecuteTransfer()\n"));
+
+ Finished = FALSE;
+ Status = EFI_SUCCESS;
+ Delay = (TimeOut * OHC_1_MILLISECOND / OHC_SYNC_POLL_INTERVAL) + 1;
+
+ for (Index = 0; Index < Delay; Index++) {
+ Finished = OhciCheckTdStatus (Ohc, Ed, IsLow, QhResult, HC_CLASS_CONTROL);
+
+ //
+ // Transfer is OK or some error occured (TD inactive)
+ //
+ if (Finished) {
+ DEBUG((EFI_D_INIT, "SUCCESS! OhciExecuteTransfer\n"));
+ break;
+ }
+
+ gBS->Stall (OHC_SYNC_POLL_INTERVAL);
+ }
+
+ if (!Finished) {
+ DEBUG ((EFI_D_ERROR, "OhciExecuteTransfer: execution not finished for %dms\n", (UINT32)TimeOut));
+ //OhciDumpQh (Qh);
+ //OhciDumpTds (Td);
+
+ Status = EFI_TIMEOUT;
+
+ } else if (QhResult->Result != EFI_USB_NOERROR) {
+ DEBUG ((EFI_D_ERROR, "OhciExecuteTransfer: execution failed with result %x\n", QhResult->Result));
+ //OhciDumpQh (Qh);
+ //OhciDumpTds (Td);
+
+ Status = EFI_DEVICE_ERROR;
+ }
+
+ DEBUG((EFI_D_INIT, "---OhciExecuteTransfer()\n"));
+
+ return Status;
+}
+
+
+/**
+ Update Async Request, QH and TDs.
+
+ @param Ohc The OHCI device.
+ @param AsyncReq The OHCI asynchronous transfer to update.
+ @param Result Transfer reslut.
+ @param NextToggle The toggle of next data.
+
+**/
+VOID
+OhciUpdateAsyncReq (
+ IN USB_HC_DEV *Ohc,
+ IN OHCI_ASYNC_REQUEST *AsyncReq,
+ IN UINT32 Result,
+ IN UINT32 NextToggle
+ )
+{
+ OHCI_ED_HW *EdHw;
+ OHCI_TD_SW *FirstTd;
+ OHCI_TD_SW *Td;
+ OHCI_TD_HW *TdHw;
+
+ UINT8 *DataPtr, *DataPhy;
+
+ //DEBUG((EFI_D_INIT, "+++OhciUpdateAsyncReq()\n"));
+
+ EdHw = AsyncReq->EdHw;
+ FirstTd = AsyncReq->TdSw;
+
+ 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) {
+ TdHw = Td->TdHw;
+ if(TdHw != NULL)
+ {
+ //
+ // Allocate and map source data buffer for bus master access.
+ //
+ DataPtr = UsbHcAllocateMem (Ohc->MemPool, Td->DataLen);
+
+ if (DataPtr == NULL) {
+ DEBUG((EFI_D_ERROR, "FAIL! UsbHcAllocateMem\n"));
+ return;
+ }
+
+ DataPhy = (UINT8 *) (UINTN) UsbHcGetPciAddressForHostMem (Ohc->MemPool, DataPtr, Td->DataLen);
+
+ Ohc->Destory = Td->Data;
+ Ohc->DestroySize = Td->DataLen;
+ TdHw->current_buf_ptr = DataPhy;
+ TdHw->buffer_end = DataPhy + Td->DataLen - 1;
+ TdHw->gtd_info.b.data_toggle = NextToggle + 2;
+
+ if(Td->NextTd)
+ {
+ TdHw->next_td = Td->NextTd->TdHw;
+ }
+
+ NextToggle = NextToggle ? 0 : 1;
+ }
+ }
+
+ EdHw->head_td_ptr = FirstTd->TdHw;
+
+ //OhciDumpEd(EdHw);
+
+ OhciLinkTdToQh (Ohc, EdHw, HC_CLASS_INTERRUPT);
+
+ //DEBUG((EFI_D_INIT, "---OhciUpdateAsyncReq()\n"));
+
+ return ;
+ }
+}
+
+
+/**
+ Create Async Request node, and Link to List.
+
+ @param Ohc The OHCI 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
+OhciCreateAsyncReq (
+ IN USB_HC_DEV *Ohc,
+ IN OHCI_ED_HW *EdHw,
+ IN OHCI_TD_SW *TdSw,
+ 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
+ )
+{
+ OHCI_ASYNC_REQUEST *AsyncReq;
+
+ DEBUG((EFI_D_INIT, "+++OhciCreateAsyncReq()\n"));
+
+ AsyncReq = AllocatePool (sizeof (OHCI_ASYNC_REQUEST));
+
+ if (AsyncReq == NULL) {
+ return EFI_OUT_OF_RESOURCES;
+ }
+
+ //
+ // Fill Request field. Data is allocated host memory, not mapped
+ //
+ AsyncReq->Signature = OHCI_ASYNC_INT_SIGNATURE;
+ AsyncReq->DevAddr = DevAddr;
+ AsyncReq->EndPoint = EndPoint;
+ AsyncReq->DataLen = DataLen;
+ AsyncReq->Interval = OhciConvertPollRate(Interval);
+ AsyncReq->Data = Data;
+ AsyncReq->Callback = Callback;
+ AsyncReq->Context = Context;
+ AsyncReq->EdHw = EdHw;
+ AsyncReq->TdSw = TdSw;
+ 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 (&(Ohc->AsyncIntList), &(AsyncReq->Link));
+
+ DEBUG((EFI_D_INIT, "---OhciCreateAsyncReq()\n"));
+
+ return EFI_SUCCESS;
+}
+
+
+/**
+ Free an asynchronous request's resource such as memory.
+
+ @param Ohc The OHCI device.
+ @param AsyncReq The asynchronous request to free.
+
+**/
+VOID
+OhciFreeAsyncReq (
+ IN USB_HC_DEV *Ohc,
+ IN OHCI_ASYNC_REQUEST *AsyncReq
+ )
+{
+ ASSERT ((Ohc != NULL) && (AsyncReq != NULL));
+
+ DEBUG((EFI_D_ERROR, "+++OhciFreeAsyncReq()\n"));
+
+ OhciDestoryTds (Ohc, AsyncReq->TdSw);
+
+ if (AsyncReq->Data != NULL) {
+ UsbHcFreeMem (Ohc->MemPool, AsyncReq->Data, AsyncReq->DataLen);
+ }
+
+ gBS->FreePool (AsyncReq);
+
+ DEBUG((EFI_D_ERROR, "+++OhciFreeAsyncReq()\n"));
+}
+
+
+/**
+ Unlink an asynchronous request's from OHC's asynchronus list.
+ also remove the queue head from the frame list. If FreeNow,
+ release its resource also. Otherwise, add the request to the
+ OHC's recycle list to wait for a while before release the memory.
+ Until then, hardware won't hold point to the request.
+
+ @param Ohc The OHCI 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
+OhciUnlinkAsyncReq (
+ IN USB_HC_DEV *Ohc,
+ IN OHCI_ASYNC_REQUEST *AsyncReq,
+ IN BOOLEAN FreeNow
+ )
+{
+ ASSERT ((Ohc != NULL) && (AsyncReq != NULL));
+
+ DEBUG((EFI_D_INIT, "+++OhciUnlinkAsyncReq()\n"));
+#if 1
+#else
+ RemoveEntryList (&(AsyncReq->Link));
+ OhciUnlinkQhFromFrameList (Ohc, AsyncReq->QhSw);
+
+ if (FreeNow) {
+ OhciFreeAsyncReq (Ohc, AsyncReq);
+ } else {
+ //
+ // To sychronize with hardware, mark the queue head as inactive
+ // then add AsyncReq to OHC's recycle list
+ //
+ AsyncReq->QhSw->QhHw.VerticalLink = QH_VLINK (NULL, TRUE);
+ AsyncReq->Recycle = Ohc->RecycleWait;
+ Ohc->RecycleWait = AsyncReq;
+ }
+#endif
+ DEBUG((EFI_D_INIT, "---OhciUnlinkAsyncReq()\n"));
+}
+
+
+/**
+ Delete Async Interrupt QH and TDs.
+
+ @param Ohc The OHCI 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
+OhciRemoveAsyncReq (
+ IN USB_HC_DEV *Ohc,
+ IN UINT8 DevAddr,
+ IN UINT8 EndPoint,
+ OUT UINT8 *Toggle
+ )
+{
+ EFI_STATUS Status;
+ OHCI_ASYNC_REQUEST *AsyncReq;
+ OHCI_QH_RESULT QhResult;
+ LIST_ENTRY *Link;
+ BOOLEAN Found;
+
+ Status = EFI_SUCCESS;
+
+ //
+ // If no asynchronous interrupt transaction exists
+ //
+ if (IsListEmpty (&(Ohc->AsyncIntList))) {
+ return EFI_SUCCESS;
+ }
+
+ //
+ // Find the asynchronous transfer to this device/endpoint pair
+ //
+ Found = FALSE;
+ Link = Ohc->AsyncIntList.ForwardLink;
+
+ do {
+ AsyncReq = OHCI_ASYNC_INT_FROM_LINK (Link);
+ Link = Link->ForwardLink;
+
+ if ((AsyncReq->DevAddr == DevAddr) && (AsyncReq->EndPoint == EndPoint)) {
+ Found = TRUE;
+ break;
+ }
+
+ } while (Link != &(Ohc->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.
+ //
+ OhciCheckTdStatus (Ohc, AsyncReq->EdHw, AsyncReq->IsLow, &QhResult, HC_CLASS_INTERRUPT);
+ *Toggle = QhResult.NextToggle;
+
+ //
+ // Don't release the request now, keep it to synchronize with hardware.
+ //
+ OhciUnlinkAsyncReq (Ohc, 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 OHC's RecycleWait list. At
+ the next time OhciMonitorAsyncReqList is fired, it is
+ moved to OHC'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 Ohc The OHCI device.
+
+**/
+VOID
+OhciRecycleAsyncReq (
+ IN USB_HC_DEV *Ohc
+ )
+{
+ OHCI_ASYNC_REQUEST *Req;
+ OHCI_ASYNC_REQUEST *Next;
+
+ Req = Ohc->Recycle;
+
+ while (Req != NULL) {
+ Next = Req->Recycle;
+ OhciFreeAsyncReq (Ohc, Req);
+ Req = Next;
+ }
+
+ Ohc->Recycle = Ohc->RecycleWait;
+ Ohc->RecycleWait = NULL;
+}
+
+
+
+/**
+ Release all the asynchronous transfers on the lsit.
+
+ @param Ohc The OHCI device.
+
+**/
+VOID
+OhciFreeAllAsyncReq (
+ IN USB_HC_DEV *Ohc
+ )
+{
+ LIST_ENTRY *Head;
+ OHCI_ASYNC_REQUEST *AsyncReq;
+
+ DEBUG((EFI_D_INIT, "+++OhciFreeAllAsyncReq()\n"));
+
+ //
+ // Call OhciRecycleAsyncReq twice. The requests on Recycle
+ // will be released at the first call; The requests on
+ // RecycleWait will be released at the second call.
+ //
+ OhciRecycleAsyncReq (Ohc);
+ OhciRecycleAsyncReq (Ohc);
+
+ Head = &(Ohc->AsyncIntList);
+
+ if (IsListEmpty (Head)) {
+ return;
+ }
+
+ while (!IsListEmpty (Head)) {
+ AsyncReq = OHCI_ASYNC_INT_FROM_LINK (Head->ForwardLink);
+ OhciUnlinkAsyncReq (Ohc, AsyncReq, TRUE);
+ }
+
+ DEBUG((EFI_D_INIT, "---OhciFreeAllAsyncReq()\n"));
+}
+
+
+/**
+ 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
+OhciMonitorAsyncReqList (
+ IN EFI_EVENT Event,
+ IN VOID *Context
+ )
+{
+ OHCI_ASYNC_REQUEST *AsyncReq;
+ LIST_ENTRY *Link;
+ USB_HC_DEV *Ohc;
+ VOID *Data;
+ BOOLEAN Finished;
+ OHCI_QH_RESULT QhResult;
+ UINT32 Inputs;
+
+ DEBUG((EFI_D_INFO, "++%a : %d\n", __FUNCTION__, __LINE__));
+
+ Ohc = (USB_HC_DEV *) Context;
+
+ //
+ // Recycle the asynchronous requests expired, and promote
+ // requests waiting to be recycled the next time when this
+ // timer expires
+ //
+
+ if(Ohc->Destory)
+ {
+ DEBUG((EFI_D_ERROR, "%a (Destoryed) : %d\n", __FUNCTION__, __LINE__));
+ UsbHcFreeMem (Ohc->MemPool, Ohc->Destory, Ohc->DestroySize);
+ Ohc->Destory = NULL;
+ Ohc->DestroySize = 0;
+ }
+ //OhciRecycleAsyncReq (Ohc);
+
+ if (IsListEmpty (&(Ohc->AsyncIntList))) {
+ DEBUG((EFI_D_ERROR, "%a (IsListEmpty) : %d\n", __FUNCTION__, __LINE__));
+ return ;
+ }
+
+ //
+ // This loop must be delete safe
+ //
+ Link = Ohc->AsyncIntList.ForwardLink;
+
+ do {
+ AsyncReq = OHCI_ASYNC_INT_FROM_LINK (Link);
+ Link = Link->ForwardLink;
+
+ Finished = OhciCheckTdStatus (Ohc, AsyncReq->EdHw, AsyncReq->IsLow, &QhResult, HC_CLASS_INTERRUPT);
+
+ if (!Finished) {
+ continue;
+ }
+
+ //
+ // Copy the data to temporary buffer if there are some
+ // data transferred. We may have zero-length packet
+ //
+ Data = NULL;
+
+ if (QhResult.Complete != 0) {
+ Data = AllocatePool (QhResult.Complete);
+
+ if (Data == NULL) {
+ return ;
+ }
+
+ CopyMem (Data, AsyncReq->TdSw->Data, QhResult.Complete);
+
+ Inputs = QhResult.Complete;
+ DEBUG((EFI_D_INIT, "INPUT : "));
+ while(Inputs--)
+ {
+ DEBUG((EFI_D_INIT, "0x%02X ", ((UINT8*)Data)[QhResult.Complete - Inputs -1]));
+ }
+ DEBUG((EFI_D_INIT, "\n"));
+ }
+
+ OhciUpdateAsyncReq (Ohc, 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 != &(Ohc->AsyncIntList));
+
+ DEBUG((EFI_D_INFO, "--%a : %d\n", __FUNCTION__, __LINE__));
+}