mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-12-01 12:53:27 +01:00
7c0aa811ec
Signed-off-by: Sergey Isakov <isakov-sl@bk.ru>
1041 lines
29 KiB
C
1041 lines
29 KiB
C
/** @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 ((EFI_D_VERBOSE, "UhciCheckTdStatus: short packet read occured\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 occured (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));
|
|
}
|