/** @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"
/**
Delete a single asynchronous interrupt transfer for
the device and endpoint.
@param Ehc The EHCI device.
@param Data Current data not associated with a QTD.
@param DataLen The length of the data.
@param PktId Packet ID to use in the QTD.
@param Toggle Data toggle to use in the QTD.
@param MaxPacket Maximu packet length of the endpoint.
@retval the pointer to the created QTD or NULL if failed to create one.
**/
PEI_EHC_QTD *
EhcCreateQtd (
IN PEI_USB2_HC_DEV *Ehc,
IN UINT8 *Data,
IN UINTN DataLen,
IN UINT8 PktId,
IN UINT8 Toggle,
IN UINTN MaxPacket
)
{
PEI_EHC_QTD *Qtd;
QTD_HW *QtdHw;
UINTN Index;
UINTN Len;
UINTN ThisBufLen;
ASSERT (Ehc != NULL);
Qtd = UsbHcAllocateMem (Ehc, Ehc->MemPool, sizeof (PEI_EHC_QTD));
if (Qtd == NULL) {
return NULL;
}
Qtd->Signature = EHC_QTD_SIG;
Qtd->Data = Data;
Qtd->DataLen = 0;
InitializeListHead (&Qtd->QtdList);
QtdHw = &Qtd->QtdHw;
QtdHw->NextQtd = QTD_LINK (NULL, TRUE);
QtdHw->AltNext = QTD_LINK (NULL, TRUE);
QtdHw->Status = QTD_STAT_ACTIVE;
QtdHw->Pid = PktId;
QtdHw->ErrCnt = QTD_MAX_ERR;
QtdHw->Ioc = 0;
QtdHw->TotalBytes = 0;
QtdHw->DataToggle = Toggle;
//
// Fill in the buffer points
//
if (Data != NULL) {
Len = 0;
for (Index = 0; Index <= QTD_MAX_BUFFER; Index++) {
//
// Set the buffer point (Check page 41 EHCI Spec 1.0). No need to
// compute the offset and clear Reserved fields. This is already
// done in the data point.
//
QtdHw->Page[Index] = EHC_LOW_32BIT (Data);
QtdHw->PageHigh[Index] = EHC_HIGH_32BIT (Data);
ThisBufLen = QTD_BUF_LEN - (EHC_LOW_32BIT (Data) & QTD_BUF_MASK);
if (Len + ThisBufLen >= DataLen) {
Len = DataLen;
break;
}
Len += ThisBufLen;
Data += ThisBufLen;
}
//
// Need to fix the last pointer if the Qtd can't hold all the
// user's data to make sure that the length is in the unit of
// max packets. If it can hold all the data, there is no such
// need.
//
if (Len < DataLen) {
Len = Len - Len % MaxPacket;
}
QtdHw->TotalBytes = (UINT32)Len;
Qtd->DataLen = Len;
}
return Qtd;
}
/**
Initialize the queue head for interrupt transfer,
that is, initialize the following three fields:
1. SplitXState in the Status field.
2. Microframe S-mask.
3. Microframe C-mask.
@param Ep The queue head's related endpoint.
@param QhHw The queue head to initialize.
**/
VOID
EhcInitIntQh (
IN USB_ENDPOINT *Ep,
IN QH_HW *QhHw
)
{
//
// Because UEFI interface can't utilitize an endpoint with
// poll rate faster than 1ms, only need to set one bit in
// the queue head. simple. But it may be changed later. If
// sub-1ms interrupt is supported, need to update the S-Mask
// here
//
if (Ep->DevSpeed == EFI_USB_SPEED_HIGH) {
QhHw->SMask = QH_MICROFRAME_0;
return;
}
//
// For low/full speed device, the transfer must go through
// the split transaction. Need to update three fields
// 1. SplitXState in the status
// 2. Microframe S-Mask
// 3. Microframe C-Mask
// UEFI USB doesn't exercise admission control. It simplely
// schedule the high speed transactions in microframe 0, and
// full/low speed transactions at microframe 1. This also
// avoid the use of FSTN.
//
QhHw->SMask = QH_MICROFRAME_1;
QhHw->CMask = QH_MICROFRAME_3 | QH_MICROFRAME_4 | QH_MICROFRAME_5;
}
/**
Allocate and initialize a EHCI queue head.
@param Ehci The EHCI device.
@param Ep The endpoint to create queue head for.
@retval the pointer to the created queue head or NULL if failed to create one.
**/
PEI_EHC_QH *
EhcCreateQh (
IN PEI_USB2_HC_DEV *Ehci,
IN USB_ENDPOINT *Ep
)
{
PEI_EHC_QH *Qh;
QH_HW *QhHw;
Qh = UsbHcAllocateMem (Ehci, Ehci->MemPool, sizeof (PEI_EHC_QH));
if (Qh == NULL) {
return NULL;
}
Qh->Signature = EHC_QH_SIG;
Qh->NextQh = NULL;
Qh->Interval = Ep->PollRate;
InitializeListHead (&Qh->Qtds);
QhHw = &Qh->QhHw;
QhHw->HorizonLink = QH_LINK (NULL, 0, TRUE);
QhHw->DeviceAddr = Ep->DevAddr;
QhHw->Inactive = 0;
QhHw->EpNum = Ep->EpAddr;
QhHw->EpSpeed = Ep->DevSpeed;
QhHw->DtCtrl = 0;
QhHw->ReclaimHead = 0;
QhHw->MaxPacketLen = (UINT32)Ep->MaxPacket;
QhHw->CtrlEp = 0;
QhHw->NakReload = QH_NAK_RELOAD;
QhHw->HubAddr = Ep->HubAddr;
QhHw->PortNum = Ep->HubPort;
QhHw->Multiplier = 1;
QhHw->DataToggle = Ep->Toggle;
if (Ep->DevSpeed != EFI_USB_SPEED_HIGH) {
QhHw->Status |= QTD_STAT_DO_SS;
}
switch (Ep->Type) {
case EHC_CTRL_TRANSFER:
//
// Special initialization for the control transfer:
// 1. Control transfer initialize data toggle from each QTD
// 2. Set the Control Endpoint Flag (C) for low/full speed endpoint.
//
QhHw->DtCtrl = 1;
if (Ep->DevSpeed != EFI_USB_SPEED_HIGH) {
QhHw->CtrlEp = 1;
}
break;
case EHC_INT_TRANSFER_ASYNC:
case EHC_INT_TRANSFER_SYNC:
//
// Special initialization for the interrupt transfer
// to set the S-Mask and C-Mask
//
QhHw->NakReload = 0;
EhcInitIntQh (Ep, QhHw);
break;
case EHC_BULK_TRANSFER:
if ((Ep->DevSpeed == EFI_USB_SPEED_HIGH) && (Ep->Direction == EfiUsbDataOut)) {
QhHw->Status |= QTD_STAT_DO_PING;
}
break;
}
return Qh;
}
/**
Convert the poll interval from application to that
be used by EHCI interface data structure. Only need
to get the max 2^n that is less than interval. UEFI
can't support high speed endpoint with a interval less
than 8 microframe because interval is specified in
the unit of ms (millisecond).
@param Interval The interval to convert.
@retval The converted interval.
**/
UINTN
EhcConvertPollRate (
IN UINTN Interval
)
{
UINTN BitCount;
if (Interval == 0) {
return 1;
}
//
// Find the index (1 based) of the highest non-zero bit
//
BitCount = 0;
while (Interval != 0) {
Interval >>= 1;
BitCount++;
}
return (UINTN)1 << (BitCount - 1);
}
/**
Free a list of QTDs.
@param Ehc The EHCI device.
@param Qtds The list head of the QTD.
**/
VOID
EhcFreeQtds (
IN PEI_USB2_HC_DEV *Ehc,
IN EFI_LIST_ENTRY *Qtds
)
{
EFI_LIST_ENTRY *Entry;
EFI_LIST_ENTRY *Next;
PEI_EHC_QTD *Qtd;
BASE_LIST_FOR_EACH_SAFE (Entry, Next, Qtds) {
Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList);
RemoveEntryList (&Qtd->QtdList);
UsbHcFreeMem (Ehc, Ehc->MemPool, Qtd, sizeof (PEI_EHC_QTD));
}
}
/**
Free an allocated URB. It is possible for it to be partially inited.
@param Ehc The EHCI device.
@param Urb The URB to free.
**/
VOID
EhcFreeUrb (
IN PEI_USB2_HC_DEV *Ehc,
IN PEI_URB *Urb
)
{
if (Urb->RequestPhy != NULL) {
IoMmuUnmap (Ehc->IoMmu, Urb->RequestMap);
}
if (Urb->DataMap != NULL) {
IoMmuUnmap (Ehc->IoMmu, Urb->DataMap);
}
if (Urb->Qh != NULL) {
//
// Ensure that this queue head has been unlinked from the
// schedule data structures. Free all the associated QTDs
//
EhcFreeQtds (Ehc, &Urb->Qh->Qtds);
UsbHcFreeMem (Ehc, Ehc->MemPool, Urb->Qh, sizeof (PEI_EHC_QH));
}
}
/**
Create a list of QTDs for the URB.
@param Ehc The EHCI device.
@param Urb The URB to create QTDs for.
@retval EFI_OUT_OF_RESOURCES Failed to allocate resource for QTD.
@retval EFI_SUCCESS The QTDs are allocated for the URB.
**/
EFI_STATUS
EhcCreateQtds (
IN PEI_USB2_HC_DEV *Ehc,
IN PEI_URB *Urb
)
{
USB_ENDPOINT *Ep;
PEI_EHC_QH *Qh;
PEI_EHC_QTD *Qtd;
PEI_EHC_QTD *StatusQtd;
PEI_EHC_QTD *NextQtd;
EFI_LIST_ENTRY *Entry;
UINT32 AlterNext;
UINT8 Toggle;
UINTN Len;
UINT8 Pid;
ASSERT ((Urb != NULL) && (Urb->Qh != NULL));
//
// EHCI follows the alternet next QTD pointer if it meets
// a short read and the AlterNext pointer is valid. UEFI
// EHCI driver should terminate the transfer except the
// control transfer.
//
Toggle = 0;
Qh = Urb->Qh;
Ep = &Urb->Ep;
StatusQtd = NULL;
AlterNext = QTD_LINK (NULL, TRUE);
if (Ep->Direction == EfiUsbDataIn) {
AlterNext = QTD_LINK (Ehc->ShortReadStop, FALSE);
}
//
// Build the Setup and status packets for control transfer
//
if (Urb->Ep.Type == EHC_CTRL_TRANSFER) {
Len = sizeof (EFI_USB_DEVICE_REQUEST);
Qtd = EhcCreateQtd (Ehc, Urb->RequestPhy, Len, QTD_PID_SETUP, 0, Ep->MaxPacket);
if (Qtd == NULL) {
return EFI_OUT_OF_RESOURCES;
}
InsertTailList (&Qh->Qtds, &Qtd->QtdList);
//
// Create the status packet now. Set the AlterNext to it. So, when
// EHCI meets a short control read, it can resume at the status stage.
// Use the opposite direction of the data stage, or IN if there is
// no data stage.
//
if (Ep->Direction == EfiUsbDataIn) {
Pid = QTD_PID_OUTPUT;
} else {
Pid = QTD_PID_INPUT;
}
StatusQtd = EhcCreateQtd (Ehc, NULL, 0, Pid, 1, Ep->MaxPacket);
if (StatusQtd == NULL) {
goto ON_ERROR;
}
if (Ep->Direction == EfiUsbDataIn) {
AlterNext = QTD_LINK (StatusQtd, FALSE);
}
Toggle = 1;
}
//
// Build the data packets for all the transfers
//
if (Ep->Direction == EfiUsbDataIn) {
Pid = QTD_PID_INPUT;
} else {
Pid = QTD_PID_OUTPUT;
}
Qtd = NULL;
Len = 0;
while (Len < Urb->DataLen) {
Qtd = EhcCreateQtd (
Ehc,
(UINT8 *)Urb->DataPhy + Len,
Urb->DataLen - Len,
Pid,
Toggle,
Ep->MaxPacket
);
if (Qtd == NULL) {
goto ON_ERROR;
}
Qtd->QtdHw.AltNext = AlterNext;
InsertTailList (&Qh->Qtds, &Qtd->QtdList);
//
// Switch the Toggle bit if odd number of packets are included in the QTD.
//
if (((Qtd->DataLen + Ep->MaxPacket - 1) / Ep->MaxPacket) % 2) {
Toggle = (UINT8)(1 - Toggle);
}
Len += Qtd->DataLen;
}
//
// Insert the status packet for control transfer
//
if (Ep->Type == EHC_CTRL_TRANSFER) {
InsertTailList (&Qh->Qtds, &StatusQtd->QtdList);
}
//
// OK, all the QTDs needed are created. Now, fix the NextQtd point
//
BASE_LIST_FOR_EACH (Entry, &Qh->Qtds) {
Qtd = EFI_LIST_CONTAINER (Entry, PEI_EHC_QTD, QtdList);
//
// break if it is the last entry on the list
//
if (Entry->ForwardLink == &Qh->Qtds) {
break;
}
NextQtd = EFI_LIST_CONTAINER (Entry->ForwardLink, PEI_EHC_QTD, QtdList);
Qtd->QtdHw.NextQtd = QTD_LINK (NextQtd, FALSE);
}
//
// Link the QTDs to the queue head
//
NextQtd = EFI_LIST_CONTAINER (Qh->Qtds.ForwardLink, PEI_EHC_QTD, QtdList);
Qh->QhHw.NextQtd = QTD_LINK (NextQtd, FALSE);
return EFI_SUCCESS;
ON_ERROR:
EhcFreeQtds (Ehc, &Qh->Qtds);
return EFI_OUT_OF_RESOURCES;
}
/**
Create a new URB and its associated QTD.
@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 Type The transaction type.
@param Request The standard USB request for control transfer.
@param Data The user data to transfer.
@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.
@retval the pointer to the created URB or NULL.
**/
PEI_URB *
EhcCreateUrb (
IN PEI_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 Type,
IN EFI_USB_DEVICE_REQUEST *Request,
IN VOID *Data,
IN UINTN DataLen,
IN EFI_ASYNC_USB_TRANSFER_CALLBACK Callback,
IN VOID *Context,
IN UINTN Interval
)
{
USB_ENDPOINT *Ep;
EFI_PHYSICAL_ADDRESS PhyAddr;
EDKII_IOMMU_OPERATION MapOp;
EFI_STATUS Status;
UINTN Len;
PEI_URB *Urb;
VOID *Map;
Map = NULL;
Urb = Ehc->Urb;
Urb->Signature = EHC_URB_SIG;
InitializeListHead (&Urb->UrbList);
Ep = &Urb->Ep;
Ep->DevAddr = DevAddr;
Ep->EpAddr = (UINT8)(EpAddr & 0x0F);
Ep->Direction = (((EpAddr & 0x80) != 0) ? EfiUsbDataIn : EfiUsbDataOut);
Ep->DevSpeed = DevSpeed;
Ep->MaxPacket = MaxPacket;
Ep->HubAddr = 0;
Ep->HubPort = 0;
if (DevSpeed != EFI_USB_SPEED_HIGH) {
ASSERT (Hub != NULL);
Ep->HubAddr = Hub->TranslatorHubAddress;
Ep->HubPort = Hub->TranslatorPortNumber;
}
Ep->Toggle = Toggle;
Ep->Type = Type;
Ep->PollRate = EhcConvertPollRate (Interval);
Urb->Request = Request;
Urb->Data = Data;
Urb->DataLen = DataLen;
Urb->Callback = Callback;
Urb->Context = Context;
Urb->Qh = EhcCreateQh (Ehc, &Urb->Ep);
if (Urb->Qh == NULL) {
goto ON_ERROR;
}
Urb->RequestPhy = NULL;
Urb->RequestMap = NULL;
Urb->DataPhy = NULL;
Urb->DataMap = NULL;
//
// Map the request and user data
//
if (Request != NULL) {
Len = sizeof (EFI_USB_DEVICE_REQUEST);
MapOp = EdkiiIoMmuOperationBusMasterRead;
Status = IoMmuMap (Ehc->IoMmu, MapOp, Request, &Len, &PhyAddr, &Map);
if (EFI_ERROR (Status) || (Len != sizeof (EFI_USB_DEVICE_REQUEST))) {
goto ON_ERROR;
}
Urb->RequestPhy = (VOID *)((UINTN)PhyAddr);
Urb->RequestMap = Map;
}
if (Data != NULL) {
Len = DataLen;
if (Ep->Direction == EfiUsbDataIn) {
MapOp = EdkiiIoMmuOperationBusMasterWrite;
} else {
MapOp = EdkiiIoMmuOperationBusMasterRead;
}
Status = IoMmuMap (Ehc->IoMmu, MapOp, Data, &Len, &PhyAddr, &Map);
if (EFI_ERROR (Status) || (Len != DataLen)) {
goto ON_ERROR;
}
Urb->DataPhy = (VOID *)((UINTN)PhyAddr);
Urb->DataMap = Map;
}
Status = EhcCreateQtds (Ehc, Urb);
if (EFI_ERROR (Status)) {
goto ON_ERROR;
}
return Urb;
ON_ERROR:
EhcFreeUrb (Ehc, Urb);
return NULL;
}