/** @file | |
This file contains URB request, each request is warpped in a | |
URB (Usb Request Block). | |
Copyright (c) 2007 - 2010, Intel Corporation. All rights reserved.<BR> | |
Copyright (c) Microsoft Corporation.<BR> | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "Ehci.h" | |
/** | |
Create a single QTD to hold the data. | |
@param Ehc The EHCI device. | |
@param Data The cpu memory address of current data not associated with a QTD. | |
@param DataPhy The pci bus address of 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. | |
@return Created QTD or NULL if failed to create one. | |
**/ | |
EHC_QTD * | |
EhcCreateQtd ( | |
IN USB2_HC_DEV *Ehc, | |
IN UINT8 *Data, | |
IN UINT8 *DataPhy, | |
IN UINTN DataLen, | |
IN UINT8 PktId, | |
IN UINT8 Toggle, | |
IN UINTN MaxPacket | |
) | |
{ | |
EHC_QTD *Qtd; | |
QTD_HW *QtdHw; | |
UINTN Index; | |
UINTN Len; | |
UINTN ThisBufLen; | |
ASSERT (Ehc != NULL); | |
Qtd = UsbHcAllocateMem (Ehc->MemPool, sizeof (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 (DataPhy); | |
QtdHw->PageHigh[Index] = EHC_HIGH_32BIT (DataPhy); | |
ThisBufLen = QTD_BUF_LEN - (EHC_LOW_32BIT (DataPhy) & QTD_BUF_MASK); | |
if (Len + ThisBufLen >= DataLen) { | |
Len = DataLen; | |
break; | |
} | |
Len += ThisBufLen; | |
Data += ThisBufLen; | |
DataPhy += 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. | |
@return Created queue head or NULL if failed to create one. | |
**/ | |
EHC_QH * | |
EhcCreateQh ( | |
IN USB2_HC_DEV *Ehci, | |
IN USB_ENDPOINT *Ep | |
) | |
{ | |
EHC_QH *Qh; | |
QH_HW *QhHw; | |
Qh = UsbHcAllocateMem (Ehci->MemPool, sizeof (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. | |
@return 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 USB2_HC_DEV *Ehc, | |
IN LIST_ENTRY *Qtds | |
) | |
{ | |
LIST_ENTRY *Entry; | |
LIST_ENTRY *Next; | |
EHC_QTD *Qtd; | |
BASE_LIST_FOR_EACH_SAFE (Entry, Next, Qtds) { | |
Qtd = EFI_LIST_CONTAINER (Entry, EHC_QTD, QtdList); | |
RemoveEntryList (&Qtd->QtdList); | |
UsbHcFreeMem (Ehc->MemPool, Qtd, sizeof (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 USB2_HC_DEV *Ehc, | |
IN URB *Urb | |
) | |
{ | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
PciIo = Ehc->PciIo; | |
if (Urb->RequestPhy != NULL) { | |
PciIo->Unmap (PciIo, Urb->RequestMap); | |
} | |
if (Urb->DataMap != NULL) { | |
PciIo->Unmap (PciIo, 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->MemPool, Urb->Qh, sizeof (EHC_QH)); | |
} | |
gBS->FreePool (Urb); | |
} | |
/** | |
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 USB2_HC_DEV *Ehc, | |
IN URB *Urb | |
) | |
{ | |
USB_ENDPOINT *Ep; | |
EHC_QH *Qh; | |
EHC_QTD *Qtd; | |
EHC_QTD *StatusQtd; | |
EHC_QTD *NextQtd; | |
LIST_ENTRY *Entry; | |
UINT32 AlterNext; | |
UINT8 Toggle; | |
UINTN Len; | |
UINT8 Pid; | |
EFI_PHYSICAL_ADDRESS PhyAddr; | |
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); | |
PhyAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, Ehc->ShortReadStop, sizeof (EHC_QTD)); | |
if (Ep->Direction == EfiUsbDataIn) { | |
AlterNext = QTD_LINK (PhyAddr, 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, (UINT8 *)Urb->Request, (UINT8 *)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, NULL, 0, Pid, 1, Ep->MaxPacket); | |
if (StatusQtd == NULL) { | |
goto ON_ERROR; | |
} | |
if (Ep->Direction == EfiUsbDataIn) { | |
PhyAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, StatusQtd, sizeof (EHC_QTD)); | |
AlterNext = QTD_LINK (PhyAddr, 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->Data + Len, | |
(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, 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, EHC_QTD, QtdList); | |
PhyAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, NextQtd, sizeof (EHC_QTD)); | |
Qtd->QtdHw.NextQtd = QTD_LINK (PhyAddr, FALSE); | |
} | |
// | |
// Link the QTDs to the queue head | |
// | |
NextQtd = EFI_LIST_CONTAINER (Qh->Qtds.ForwardLink, EHC_QTD, QtdList); | |
PhyAddr = UsbHcGetPciAddressForHostMem (Ehc->MemPool, NextQtd, sizeof (EHC_QTD)); | |
Qh->QhHw.NextQtd = QTD_LINK (PhyAddr, 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. | |
@return Created URB or NULL. | |
**/ | |
URB * | |
EhcCreateUrb ( | |
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 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; | |
EFI_PCI_IO_PROTOCOL_OPERATION MapOp; | |
EFI_PCI_IO_PROTOCOL *PciIo; | |
EFI_STATUS Status; | |
UINTN Len; | |
URB *Urb; | |
VOID *Map; | |
Urb = AllocateZeroPool (sizeof (URB)); | |
if (Urb == NULL) { | |
return NULL; | |
} | |
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; | |
PciIo = Ehc->PciIo; | |
Urb->Qh = EhcCreateQh (Ehc, &Urb->Ep); | |
if (Urb->Qh == NULL) { | |
goto ON_ERROR; | |
} | |
// | |
// Map the request and user data | |
// | |
if (Request != NULL) { | |
Len = sizeof (EFI_USB_DEVICE_REQUEST); | |
MapOp = EfiPciIoOperationBusMasterRead; | |
Status = PciIo->Map (PciIo, 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 = EfiPciIoOperationBusMasterWrite; | |
} else { | |
MapOp = EfiPciIoOperationBusMasterRead; | |
} | |
Status = PciIo->Map (PciIo, 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; | |
} |