mirror of https://github.com/acidanthera/audk.git
460 lines
12 KiB
C
460 lines
12 KiB
C
/** @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.<BR>
|
|
Copyright (c) Microsoft Corporation.<BR>
|
|
|
|
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;
|
|
}
|