/** @file PEIM to produce gPeiUsb2HostControllerPpiGuid based on gPeiUsbControllerPpiGuid which is used to enable recovery function from USB Drivers. Copyright (c) 2010 - 2018, Intel Corporation. All rights reserved.
Copyright (c) Microsoft Corporation.
SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include "EhcPeim.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 PEI_USB2_HC_DEV *Ehc ) { USB_ENDPOINT Ep; PEI_EHC_QH *Qh; QH_HW *QhHw; PEI_EHC_QTD *Qtd; // // Create an inactive Qtd to terminate the short packet read. // Qtd = EhcCreateQtd (Ehc, 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; } QhHw = &Qh->QhHw; QhHw->HorizonLink = QH_LINK (QhHw, EHC_TYPE_QH, FALSE); QhHw->Status = QTD_STAT_HALTED; QhHw->ReclaimHead = 1; 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 for. @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 PEI_USB2_HC_DEV *Ehc ) { VOID *Buf; EFI_PHYSICAL_ADDRESS PhyAddr; VOID *Map; UINTN Index; UINT32 *Desc; 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 // // // The Frame List ocupies 4K bytes, // and must be aligned on 4-Kbyte boundaries. // Status = IoMmuAllocateBuffer ( Ehc->IoMmu, 1, &Buf, &PhyAddr, &Map ); if (EFI_ERROR (Status) || (Buf == NULL)) { return EFI_OUT_OF_RESOURCES; } Ehc->PeriodFrame = Buf; Ehc->PeriodFrameMap = Map; Ehc->High32bitAddr = 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 ( Ehc, EHC_BIT_IS_SET (Ehc->HcCapParams, HCCP_64BIT), Ehc->High32bitAddr ); if (Ehc->MemPool == NULL) { return EFI_OUT_OF_RESOURCES; } Status = EhcCreateHelpQ (Ehc); if (EFI_ERROR (Status)) { return Status; } // // Initialize the frame list entries then set the registers // Desc = (UINT32 *)Ehc->PeriodFrame; PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->PeriodOne, sizeof (PEI_EHC_QH)); for (Index = 0; Index < EHC_FRAME_LEN; Index++) { Desc[Index] = QH_LINK (PciAddr, EHC_TYPE_QH, FALSE); } EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, EHC_LOW_32BIT (PhyAddr)); // // Second initialize the asynchronous schedule: // Only need to set the AsynListAddr register to // the reclamation header // PciAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH)); EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, EHC_LOW_32BIT (PciAddr)); return EFI_SUCCESS; } /** Free the schedule data. It may be partially initialized. @param Ehc The EHCI device. **/ VOID EhcFreeSched ( IN PEI_USB2_HC_DEV *Ehc ) { EhcWriteOpReg (Ehc, EHC_FRAME_BASE_OFFSET, 0); EhcWriteOpReg (Ehc, EHC_ASYNC_HEAD_OFFSET, 0); if (Ehc->PeriodOne != NULL) { UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->PeriodOne, sizeof (PEI_EHC_QH)); Ehc->PeriodOne = NULL; } if (Ehc->ReclaimHead != NULL) { UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->ReclaimHead, sizeof (PEI_EHC_QH)); Ehc->ReclaimHead = NULL; } if (Ehc->ShortReadStop != NULL) { UsbHcFreeMem (Ehc, Ehc->MemPool, Ehc->ShortReadStop, sizeof (PEI_EHC_QTD)); Ehc->ShortReadStop = NULL; } if (Ehc->MemPool != NULL) { UsbHcFreeMemPool (Ehc, Ehc->MemPool); Ehc->MemPool = NULL; } if (Ehc->PeriodFrame != NULL) { IoMmuFreeBuffer (Ehc->IoMmu, 1, Ehc->PeriodFrame, Ehc->PeriodFrameMap); Ehc->PeriodFrame = 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 PEI_USB2_HC_DEV *Ehc, IN PEI_EHC_QH *Qh ) { PEI_EHC_QH *Head; // // 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; Qh->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE); Head->QhHw.HorizonLink = QH_LINK (Qh, 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 PEI_USB2_HC_DEV *Ehc, IN PEI_EHC_QH *Qh ) { PEI_EHC_QH *Head; 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; Head->QhHw.HorizonLink = QH_LINK (Head, EHC_TYPE_QH, FALSE); // // Set and wait the door bell to synchronize with the hardware // EhcSetAndWaitDoorBell (Ehc, EHC_GENERIC_TIMEOUT); return; } /** 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. @retval TRUE URB transfer is finialized. @retval FALSE URB transfer is not finialized. **/ BOOLEAN EhcCheckUrbResult ( IN PEI_USB2_HC_DEV *Ehc, IN PEI_URB *Urb ) { EFI_LIST_ENTRY *Entry; PEI_EHC_QTD *Qtd; QTD_HW *QtdHw; UINT8 State; BOOLEAN Finished; 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, PEI_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)) { // EHC_DUMP_QH ((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 // if (QtdHw->AltNext == QTD_LINK (Ehc->ShortReadStop, FALSE)) { Finished = TRUE; goto ON_EXIT; } } } } 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. @retval EFI_DEVICE_ERROR The transfer failed due to transfer error. @retval EFI_TIMEOUT The transfer failed due to time out. @retval EFI_SUCCESS The transfer finished OK. **/ EFI_STATUS EhcExecTransfer ( IN PEI_USB2_HC_DEV *Ehc, IN PEI_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; // // 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; } MicroSecondDelay (EHC_1_MICROSECOND); } if (!Finished) { Status = EFI_TIMEOUT; } else if (Urb->Result != EFI_USB_NOERROR) { Status = EFI_DEVICE_ERROR; } return Status; }