/** @file EHCI transfer scheduling routines. Copyright (c) 2007 - 2018, Intel Corporation. All rights reserved.
Copyright (c) Microsoft Corporation.
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 ((EFI_D_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 ((EFI_D_VERBOSE, "EhcCheckUrbResult: Short packet read, break\n")); Finished = TRUE; goto ON_EXIT; } DEBUG ((EFI_D_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 ((EFI_D_ERROR, "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 ((EFI_D_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", __FUNCTION__)); 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", __FUNCTION__)); 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 ((EFI_D_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); }