/** @file | |
EHCI transfer scheduling routines. | |
Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.<BR> | |
Copyright (c) Microsoft Corporation.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "Ehci.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 USB2_HC_DEV *Ehc | |
) | |
{ | |
USB_ENDPOINT Ep; | |
EHC_QH *Qh; | |
QH_HW *QhHw; | |
EHC_QTD *Qtd; | |
EFI_PHYSICAL_ADDRESS PciAddr; | |
// | |
// Create an inactive Qtd to terminate the short packet read. | |
// | |
Qtd = EhcCreateQtd (Ehc, NULL, 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; | |
} | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Qh, sizeof (EHC_QH)); | |
QhHw = &Qh->QhHw; | |
QhHw->HorizonLink = QH_LINK (PciAddr + OFFSET_OF (EHC_QH, QhHw), EHC_TYPE_QH, FALSE); | |
QhHw->Status = QTD_STAT_HALTED; | |
QhHw->ReclaimHead = 1; | |
Qh->NextQh = Qh; | |
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. | |
@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 USB2_HC_DEV *Ehc | |
) | |
{ | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
VOID *Buf; | |
EFI_PHYSICAL_ADDRESS PhyAddr; | |
VOID *Map; | |
UINTN Pages; | |
UINTN Bytes; | |
UINTN Index; | |
EFI_STATUS Status; | |
EFI_PHYSICAL_ADDRESS PciAddr; | |
// | |
// First initialize the periodical schedule data: | |
// 1. Allocate and map the memory for the frame list | |
// 2. Create the help QTD/QH | |
// 3. Initialize the frame entries | |
// 4. Set the frame list register | |
// | |
PciIo = Ehc->PciIo; | |
Bytes = 4096; | |
Pages = EFI_SIZE_TO_PAGES (Bytes); | |
Status = PciIo->AllocateBuffer ( | |
PciIo, | |
AllocateAnyPages, | |
EfiBootServicesData, | |
Pages, | |
&Buf, | |
0 | |
); | |
if (EFI_ERROR (Status)) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = PciIo->Map ( | |
PciIo, | |
EfiPciIoOperationBusMasterCommonBuffer, | |
Buf, | |
&Bytes, | |
&PhyAddr, | |
&Map | |
); | |
if (EFI_ERROR (Status) || (Bytes != 4096)) { | |
PciIo->FreeBuffer (PciIo, Pages, Buf); | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Ehc->PeriodFrame = Buf; | |
Ehc->PeriodFrameMap = Map; | |
// | |
// Program the FRAMELISTBASE register with the low 32 bit addr | |
// | |
EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, EHC_LOW_32BIT (PhyAddr)); | |
// | |
// Program the CTRLDSSEGMENT register with the high 32 bit addr | |
// | |
EhcWriteOpReg (Ehc, EHC_CTRLDSSEG_OFFSET, 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 ( | |
PciIo, | |
Ehc->Support64BitDma, | |
EHC_HIGH_32BIT (PhyAddr) | |
); | |
if (Ehc->MemPool == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto ErrorExit1; | |
} | |
Status = EhcCreateHelpQ (Ehc); | |
if (EFI_ERROR (Status)) { | |
goto ErrorExit; | |
} | |
// | |
// Initialize the frame list entries then set the registers | |
// | |
Ehc->PeriodFrameHost = AllocateZeroPool (EHC_FRAME_LEN * sizeof (UINTN)); | |
if (Ehc->PeriodFrameHost == NULL) { | |
Status = EFI_OUT_OF_RESOURCES; | |
goto ErrorExit; | |
} | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->PeriodOne, sizeof (EHC_QH)); | |
for (Index = 0; Index < EHC_FRAME_LEN; Index++) { | |
// | |
// Store the pci bus address of the QH in period frame list which will be accessed by pci bus master. | |
// | |
((UINT32 *)(Ehc->PeriodFrame))[Index] = QH_LINK (PciAddr, EHC_TYPE_QH, FALSE); | |
// | |
// Store the host address of the QH in period frame list which will be accessed by host. | |
// | |
((UINTN *)(Ehc->PeriodFrameHost))[Index] = (UINTN)Ehc->PeriodOne; | |
} | |
// | |
// Second initialize the asynchronous schedule: | |
// Only need to set the AsynListAddr register to | |
// the reclamation header | |
// | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (EHC_QH)); | |
EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, EHC_LOW_32BIT (PciAddr)); | |
return EFI_SUCCESS; | |
ErrorExit: | |
if (Ehc->PeriodOne != NULL) { | |
UsbHcFreeMem (Ehc->MemPool, Ehc->PeriodOne, sizeof (EHC_QH)); | |
Ehc->PeriodOne = NULL; | |
} | |
if (Ehc->ReclaimHead != NULL) { | |
UsbHcFreeMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (EHC_QH)); | |
Ehc->ReclaimHead = NULL; | |
} | |
if (Ehc->ShortReadStop != NULL) { | |
UsbHcFreeMem (Ehc->MemPool, Ehc->ShortReadStop, sizeof (EHC_QTD)); | |
Ehc->ShortReadStop = NULL; | |
} | |
ErrorExit1: | |
PciIo->FreeBuffer (PciIo, Pages, Buf); | |
PciIo->Unmap (PciIo, Map); | |
return Status; | |
} | |
/** | |
Free the schedule data. It may be partially initialized. | |
@param Ehc The EHCI device. | |
**/ | |
VOID | |
EhcFreeSched ( | |
IN USB2_HC_DEV *Ehc | |
) | |
{ | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, 0); | |
EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, 0); | |
if (Ehc->PeriodOne != NULL) { | |
UsbHcFreeMem (Ehc->MemPool, Ehc->PeriodOne, sizeof (EHC_QH)); | |
Ehc->PeriodOne = NULL; | |
} | |
if (Ehc->ReclaimHead != NULL) { | |
UsbHcFreeMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (EHC_QH)); | |
Ehc->ReclaimHead = NULL; | |
} | |
if (Ehc->ShortReadStop != NULL) { | |
UsbHcFreeMem (Ehc->MemPool, Ehc->ShortReadStop, sizeof (EHC_QTD)); | |
Ehc->ShortReadStop = NULL; | |
} | |
if (Ehc->MemPool != NULL) { | |
UsbHcFreeMemPool (Ehc->MemPool); | |
Ehc->MemPool = NULL; | |
} | |
if (Ehc->PeriodFrame != NULL) { | |
PciIo = Ehc->PciIo; | |
ASSERT (PciIo != NULL); | |
PciIo->Unmap (PciIo, Ehc->PeriodFrameMap); | |
PciIo->FreeBuffer ( | |
PciIo, | |
EFI_SIZE_TO_PAGES (EFI_PAGE_SIZE), | |
Ehc->PeriodFrame | |
); | |
Ehc->PeriodFrame = NULL; | |
} | |
if (Ehc->PeriodFrameHost != NULL) { | |
FreePool (Ehc->PeriodFrameHost); | |
Ehc->PeriodFrameHost = 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 USB2_HC_DEV *Ehc, | |
IN EHC_QH *Qh | |
) | |
{ | |
EHC_QH *Head; | |
EFI_PHYSICAL_ADDRESS PciAddr; | |
// | |
// 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; | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Qh->NextQh, sizeof (EHC_QH)); | |
Qh->QhHw.HorizonLink = QH_LINK (PciAddr, EHC_TYPE_QH, FALSE); | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Head->NextQh, sizeof (EHC_QH)); | |
Head->QhHw.HorizonLink = QH_LINK (PciAddr, 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 USB2_HC_DEV *Ehc, | |
IN EHC_QH *Qh | |
) | |
{ | |
EHC_QH *Head; | |
EFI_STATUS Status; | |
EFI_PHYSICAL_ADDRESS PciAddr; | |
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; | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Head->NextQh, sizeof (EHC_QH)); | |
Head->QhHw.HorizonLink = QH_LINK (PciAddr, EHC_TYPE_QH, FALSE); | |
// | |
// Set and wait the door bell to synchronize with the hardware | |
// | |
Status = EhcSetAndWaitDoorBell (Ehc, EHC_GENERIC_TIMEOUT); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcUnlinkQhFromAsync: Failed to synchronize with doorbell\n")); | |
} | |
} | |
/** | |
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 USB2_HC_DEV *Ehc, | |
IN EHC_QH *Qh | |
) | |
{ | |
UINTN Index; | |
EHC_QH *Prev; | |
EHC_QH *Next; | |
EFI_PHYSICAL_ADDRESS PciAddr; | |
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 (((UINT32 *)Ehc->PeriodFrame)[Index])); | |
Next = (EHC_QH *)((UINTN *)Ehc->PeriodFrameHost)[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; | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Qh, sizeof (EHC_QH)); | |
Prev->QhHw.HorizonLink = QH_LINK (PciAddr, 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; | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Next, sizeof (EHC_QH)); | |
Qh->QhHw.HorizonLink = QH_LINK (PciAddr, EHC_TYPE_QH, FALSE); | |
} | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Qh, sizeof (EHC_QH)); | |
if (Prev == NULL) { | |
((UINT32 *)Ehc->PeriodFrame)[Index] = QH_LINK (PciAddr, EHC_TYPE_QH, FALSE); | |
((UINTN *)Ehc->PeriodFrameHost)[Index] = (UINTN)Qh; | |
} else { | |
Prev->NextQh = Qh; | |
Prev->QhHw.HorizonLink = QH_LINK (PciAddr, 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 USB2_HC_DEV *Ehc, | |
IN EHC_QH *Qh | |
) | |
{ | |
UINTN Index; | |
EHC_QH *Prev; | |
EHC_QH *This; | |
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 (((UINT32 *)Ehc->PeriodFrame)[Index])); | |
This = (EHC_QH *)((UINTN *)Ehc->PeriodFrameHost)[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 | |
// | |
((UINT32 *)Ehc->PeriodFrame)[Index] = Qh->QhHw.HorizonLink; | |
((UINTN *)Ehc->PeriodFrameHost)[Index] = (UINTN)Qh->NextQh; | |
} 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. | |
@return Whether the result of URB transfer is finialized. | |
**/ | |
BOOLEAN | |
EhcCheckUrbResult ( | |
IN USB2_HC_DEV *Ehc, | |
IN URB *Urb | |
) | |
{ | |
LIST_ENTRY *Entry; | |
EHC_QTD *Qtd; | |
QTD_HW *QtdHw; | |
UINT8 State; | |
BOOLEAN Finished; | |
EFI_PHYSICAL_ADDRESS PciAddr; | |
ASSERT ((Ehc != NULL) && (Urb != NULL) && (Urb->Qh != NULL)); | |
Finished = TRUE; | |
Urb->Completed = 0; | |
Urb->Result = EFI_USB_NOERROR; | |
if (EhcIsHalt (Ehc) || EhcIsSysError (Ehc)) { | |
Urb->Result |= EFI_USB_ERR_SYSTEM; | |
goto ON_EXIT; | |
} | |
BASE_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) { | |
Qtd = EFI_LIST_CONTAINER (Entry, 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)) { | |
EhcDumpQh (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 | |
// | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->ShortReadStop, sizeof (EHC_QTD)); | |
if (QtdHw->AltNext == QTD_LINK (PciAddr, FALSE)) { | |
DEBUG ((DEBUG_VERBOSE, "EhcCheckUrbResult: Short packet read, break\n")); | |
Finished = TRUE; | |
goto ON_EXIT; | |
} | |
DEBUG ((DEBUG_VERBOSE, "EhcCheckUrbResult: Short packet read, continue\n")); | |
} | |
} | |
} | |
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. | |
@return EFI_DEVICE_ERROR The transfer failed due to transfer error. | |
@return EFI_TIMEOUT The transfer failed due to time out. | |
@return EFI_SUCCESS The transfer finished OK. | |
**/ | |
EFI_STATUS | |
EhcExecTransfer ( | |
IN USB2_HC_DEV *Ehc, | |
IN URB *Urb, | |
IN UINTN TimeOut | |
) | |
{ | |
EFI_STATUS Status; | |
UINTN Index; | |
UINTN Loop; | |
BOOLEAN Finished; | |
BOOLEAN InfiniteLoop; | |
Status = EFI_SUCCESS; | |
Loop = TimeOut * EHC_1_MILLISECOND; | |
Finished = FALSE; | |
InfiniteLoop = FALSE; | |
// | |
// 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 < Loop); Index++) { | |
Finished = EhcCheckUrbResult (Ehc, Urb); | |
if (Finished) { | |
break; | |
} | |
gBS->Stall (EHC_1_MICROSECOND); | |
} | |
if (!Finished) { | |
DEBUG ((DEBUG_VERBOSE, "EhcExecTransfer: transfer not finished in %dms\n", (UINT32)TimeOut)); | |
EhcDumpQh (Urb->Qh, NULL, FALSE); | |
Status = EFI_TIMEOUT; | |
} else if (Urb->Result != EFI_USB_NOERROR) { | |
DEBUG ((DEBUG_ERROR, "EhcExecTransfer: transfer failed with %x\n", Urb->Result)); | |
EhcDumpQh (Urb->Qh, NULL, FALSE); | |
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_SUCCESS An asynchronous transfer is removed. | |
@retval EFI_NOT_FOUND No transfer for the device is found. | |
**/ | |
EFI_STATUS | |
EhciDelAsyncIntTransfer ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT8 DevAddr, | |
IN UINT8 EpNum, | |
OUT UINT8 *DataToggle | |
) | |
{ | |
LIST_ENTRY *Entry; | |
LIST_ENTRY *Next; | |
URB *Urb; | |
EFI_USB_DATA_DIRECTION Direction; | |
Direction = (((EpNum & 0x80) != 0) ? EfiUsbDataIn : EfiUsbDataOut); | |
EpNum &= 0x0F; | |
BASE_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) { | |
Urb = EFI_LIST_CONTAINER (Entry, 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); | |
gBS->FreePool (Urb->Data); | |
EhcFreeUrb (Ehc, Urb); | |
return EFI_SUCCESS; | |
} | |
} | |
return EFI_NOT_FOUND; | |
} | |
/** | |
Remove all the asynchronous interrutp transfers. | |
@param Ehc The EHCI device. | |
**/ | |
VOID | |
EhciDelAllAsyncIntTransfers ( | |
IN USB2_HC_DEV *Ehc | |
) | |
{ | |
LIST_ENTRY *Entry; | |
LIST_ENTRY *Next; | |
URB *Urb; | |
BASE_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) { | |
Urb = EFI_LIST_CONTAINER (Entry, URB, UrbList); | |
EhcUnlinkQhFromPeriod (Ehc, Urb->Qh); | |
RemoveEntryList (&Urb->UrbList); | |
gBS->FreePool (Urb->Data); | |
EhcFreeUrb (Ehc, Urb); | |
} | |
} | |
/** | |
Insert a single asynchronous interrupt transfer for | |
the device and endpoint. | |
@param Ehc The EHCI device. | |
@param DevAddr The device address. | |
@param EpAddr Endpoint addrress & its direction. | |
@param DevSpeed The device speed. | |
@param Toggle Initial data toggle to use. | |
@param MaxPacket The max packet length of the endpoint. | |
@param Hub The transaction translator to use. | |
@param DataLen The length of data buffer. | |
@param Callback The function to call when data is transferred. | |
@param Context The context to the callback. | |
@param Interval The interval for interrupt transfer. | |
@return Created URB or NULL. | |
**/ | |
URB * | |
EhciInsertAsyncIntTransfer ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT8 DevAddr, | |
IN UINT8 EpAddr, | |
IN UINT8 DevSpeed, | |
IN UINT8 Toggle, | |
IN UINTN MaxPacket, | |
IN EFI_USB2_HC_TRANSACTION_TRANSLATOR *Hub, | |
IN UINTN DataLen, | |
IN EFI_ASYNC_USB_TRANSFER_CALLBACK Callback, | |
IN VOID *Context, | |
IN UINTN Interval | |
) | |
{ | |
VOID *Data; | |
URB *Urb; | |
Data = AllocatePool (DataLen); | |
if (Data == NULL) { | |
DEBUG ((DEBUG_ERROR, "%a: failed to allocate buffer\n", __func__)); | |
return NULL; | |
} | |
Urb = EhcCreateUrb ( | |
Ehc, | |
DevAddr, | |
EpAddr, | |
DevSpeed, | |
Toggle, | |
MaxPacket, | |
Hub, | |
EHC_INT_TRANSFER_ASYNC, | |
NULL, | |
Data, | |
DataLen, | |
Callback, | |
Context, | |
Interval | |
); | |
if (Urb == NULL) { | |
DEBUG ((DEBUG_ERROR, "%a: failed to create URB\n", __func__)); | |
gBS->FreePool (Data); | |
return NULL; | |
} | |
// | |
// New asynchronous transfer must inserted to the head. | |
// Check the comments in EhcMoniteAsyncRequests | |
// | |
EhcLinkQhToPeriod (Ehc, Urb->Qh); | |
InsertHeadList (&Ehc->AsyncIntTransfers, &Urb->UrbList); | |
return 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_SUCCESS Success to flush data to mapped system memory. | |
@retval EFI_DEVICE_ERROR Fail to flush data to mapped system memory. | |
**/ | |
EFI_STATUS | |
EhcFlushAsyncIntMap ( | |
IN USB2_HC_DEV *Ehc, | |
IN URB *Urb | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_PHYSICAL_ADDRESS PhyAddr; | |
EFI_PCI_IO_PROTOCOL_OPERATION MapOp; | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
UINTN Len; | |
VOID *Map; | |
PciIo = Ehc->PciIo; | |
Len = Urb->DataLen; | |
if (Urb->Ep.Direction == EfiUsbDataIn) { | |
MapOp = EfiPciIoOperationBusMasterWrite; | |
} else { | |
MapOp = EfiPciIoOperationBusMasterRead; | |
} | |
Status = PciIo->Unmap (PciIo, Urb->DataMap); | |
if (EFI_ERROR (Status)) { | |
goto ON_ERROR; | |
} | |
Urb->DataMap = NULL; | |
Status = PciIo->Map (PciIo, MapOp, Urb->Data, &Len, &PhyAddr, &Map); | |
if (EFI_ERROR (Status) || (Len != Urb->DataLen)) { | |
goto ON_ERROR; | |
} | |
Urb->DataPhy = (VOID *)((UINTN)PhyAddr); | |
Urb->DataMap = Map; | |
return EFI_SUCCESS; | |
ON_ERROR: | |
return EFI_DEVICE_ERROR; | |
} | |
/** | |
Update the queue head for next round of asynchronous transfer. | |
@param Ehc The EHCI device. | |
@param Urb The URB to update. | |
**/ | |
VOID | |
EhcUpdateAsyncRequest ( | |
IN USB2_HC_DEV *Ehc, | |
IN URB *Urb | |
) | |
{ | |
LIST_ENTRY *Entry; | |
EHC_QTD *FirstQtd; | |
QH_HW *QhHw; | |
EHC_QTD *Qtd; | |
QTD_HW *QtdHw; | |
UINTN Index; | |
EFI_PHYSICAL_ADDRESS PciAddr; | |
Qtd = NULL; | |
if (Urb->Result == EFI_USB_NOERROR) { | |
FirstQtd = NULL; | |
BASE_LIST_FOR_EACH (Entry, &Urb->Qh->Qtds) { | |
Qtd = EFI_LIST_CONTAINER (Entry, 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; | |
// | |
// calculate physical address by offset. | |
// | |
PciAddr = (UINTN)Urb->DataPhy + ((UINTN)Qtd->Data - (UINTN)Urb->Data); | |
QtdHw->Page[0] = EHC_LOW_32BIT (PciAddr); | |
QtdHw->PageHigh[0] = EHC_HIGH_32BIT (PciAddr); | |
} | |
// | |
// 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; | |
} | |
PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, FirstQtd, sizeof (EHC_QTD)); | |
QhHw->NextQtd = QTD_LINK (PciAddr, FALSE); | |
} | |
return; | |
} | |
/** | |
Interrupt transfer periodic check handler. | |
@param Event Interrupt event. | |
@param Context Pointer to USB2_HC_DEV. | |
**/ | |
VOID | |
EFIAPI | |
EhcMonitorAsyncRequests ( | |
IN EFI_EVENT Event, | |
IN VOID *Context | |
) | |
{ | |
USB2_HC_DEV *Ehc; | |
EFI_TPL OldTpl; | |
LIST_ENTRY *Entry; | |
LIST_ENTRY *Next; | |
BOOLEAN Finished; | |
UINT8 *ProcBuf; | |
URB *Urb; | |
EFI_STATUS Status; | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
Ehc = (USB2_HC_DEV *)Context; | |
BASE_LIST_FOR_EACH_SAFE (Entry, Next, &Ehc->AsyncIntTransfers) { | |
Urb = EFI_LIST_CONTAINER (Entry, 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); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_ERROR, "EhcMonitorAsyncRequests: Fail to Flush AsyncInt Mapped Memeory\n")); | |
} | |
// | |
// 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) { | |
// | |
// Make sure the data received from HW is no more than expected. | |
// | |
if (Urb->Completed <= Urb->DataLen) { | |
ProcBuf = AllocatePool (Urb->Completed); | |
} | |
if (ProcBuf == NULL) { | |
EhcUpdateAsyncRequest (Ehc, Urb); | |
continue; | |
} | |
CopyMem (ProcBuf, Urb->Data, Urb->Completed); | |
} | |
EhcUpdateAsyncRequest (Ehc, 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) { | |
// | |
// Restore the old TPL, USB bus maybe connect device in | |
// his callback. Some drivers may has a lower TPL restriction. | |
// | |
gBS->RestoreTPL (OldTpl); | |
(Urb->Callback)(ProcBuf, Urb->Completed, Urb->Context, Urb->Result); | |
OldTpl = gBS->RaiseTPL (EHC_TPL); | |
} | |
if (ProcBuf != NULL) { | |
FreePool (ProcBuf); | |
} | |
} | |
gBS->RestoreTPL (OldTpl); | |
} |