mirror of https://github.com/acidanthera/audk.git
702 lines
17 KiB
C
702 lines
17 KiB
C
/** @file
|
|
|
|
The UHCI register operation routines.
|
|
|
|
Copyright (c) 2007 - 2010, Intel Corporation. All rights reserved.<BR>
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
|
|
**/
|
|
|
|
#include "Uhci.h"
|
|
|
|
|
|
/**
|
|
Map address of request structure buffer.
|
|
|
|
@param Uhc The UHCI device.
|
|
@param Request The user request buffer.
|
|
@param MappedAddr Mapped address of request.
|
|
@param Map Identificaion of this mapping to return.
|
|
|
|
@return EFI_SUCCESS Success.
|
|
@return EFI_DEVICE_ERROR Fail to map the user request.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
UhciMapUserRequest (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN OUT VOID *Request,
|
|
OUT UINT8 **MappedAddr,
|
|
OUT VOID **Map
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
UINTN Len;
|
|
EFI_PHYSICAL_ADDRESS PhyAddr;
|
|
|
|
Len = sizeof (EFI_USB_DEVICE_REQUEST);
|
|
Status = Uhc->PciIo->Map (
|
|
Uhc->PciIo,
|
|
EfiPciIoOperationBusMasterRead,
|
|
Request,
|
|
&Len,
|
|
&PhyAddr,
|
|
Map
|
|
);
|
|
|
|
if (!EFI_ERROR (Status)) {
|
|
*MappedAddr = (UINT8 *) (UINTN) PhyAddr;
|
|
}
|
|
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Map address of user data buffer.
|
|
|
|
@param Uhc The UHCI device.
|
|
@param Direction Direction of the data transfer.
|
|
@param Data The user data buffer.
|
|
@param Len Length of the user data.
|
|
@param PktId Packet identificaion.
|
|
@param MappedAddr Mapped address to return.
|
|
@param Map Identificaion of this mapping to return.
|
|
|
|
@return EFI_SUCCESS Success.
|
|
@return EFI_DEVICE_ERROR Fail to map the user data.
|
|
|
|
**/
|
|
EFI_STATUS
|
|
UhciMapUserData (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN EFI_USB_DATA_DIRECTION Direction,
|
|
IN VOID *Data,
|
|
IN OUT UINTN *Len,
|
|
OUT UINT8 *PktId,
|
|
OUT UINT8 **MappedAddr,
|
|
OUT VOID **Map
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_PHYSICAL_ADDRESS PhyAddr;
|
|
|
|
Status = EFI_SUCCESS;
|
|
|
|
switch (Direction) {
|
|
case EfiUsbDataIn:
|
|
//
|
|
// BusMasterWrite means cpu read
|
|
//
|
|
*PktId = INPUT_PACKET_ID;
|
|
Status = Uhc->PciIo->Map (
|
|
Uhc->PciIo,
|
|
EfiPciIoOperationBusMasterWrite,
|
|
Data,
|
|
Len,
|
|
&PhyAddr,
|
|
Map
|
|
);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
*MappedAddr = (UINT8 *) (UINTN) PhyAddr;
|
|
break;
|
|
|
|
case EfiUsbDataOut:
|
|
*PktId = OUTPUT_PACKET_ID;
|
|
Status = Uhc->PciIo->Map (
|
|
Uhc->PciIo,
|
|
EfiPciIoOperationBusMasterRead,
|
|
Data,
|
|
Len,
|
|
&PhyAddr,
|
|
Map
|
|
);
|
|
|
|
if (EFI_ERROR (Status)) {
|
|
goto EXIT;
|
|
}
|
|
|
|
*MappedAddr = (UINT8 *) (UINTN) PhyAddr;
|
|
break;
|
|
|
|
case EfiUsbNoData:
|
|
if ((Len != NULL) && (*Len != 0)) {
|
|
Status = EFI_INVALID_PARAMETER;
|
|
goto EXIT;
|
|
}
|
|
|
|
*PktId = OUTPUT_PACKET_ID;
|
|
*MappedAddr = NULL;
|
|
*Map = NULL;
|
|
break;
|
|
|
|
default:
|
|
Status = EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
EXIT:
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Link the TD To QH.
|
|
|
|
@param Uhc The UHCI device.
|
|
@param Qh The queue head for the TD to link to.
|
|
@param Td The TD to link.
|
|
|
|
**/
|
|
VOID
|
|
UhciLinkTdToQh (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN UHCI_QH_SW *Qh,
|
|
IN UHCI_TD_SW *Td
|
|
)
|
|
{
|
|
EFI_PHYSICAL_ADDRESS PhyAddr;
|
|
|
|
PhyAddr = UsbHcGetPciAddressForHostMem (Uhc->MemPool, Td, sizeof (UHCI_TD_HW));
|
|
|
|
ASSERT ((Qh != NULL) && (Td != NULL));
|
|
|
|
Qh->QhHw.VerticalLink = QH_VLINK (PhyAddr, FALSE);
|
|
Qh->TDs = (VOID *) Td;
|
|
}
|
|
|
|
|
|
/**
|
|
Unlink TD from the QH.
|
|
|
|
@param Qh The queue head to unlink from.
|
|
@param Td The TD to unlink.
|
|
|
|
**/
|
|
VOID
|
|
UhciUnlinkTdFromQh (
|
|
IN UHCI_QH_SW *Qh,
|
|
IN UHCI_TD_SW *Td
|
|
)
|
|
{
|
|
ASSERT ((Qh != NULL) && (Td != NULL));
|
|
|
|
Qh->QhHw.VerticalLink = QH_VLINK (NULL, TRUE);
|
|
Qh->TDs = NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
Append a new TD To the previous TD.
|
|
|
|
@param Uhc The UHCI device.
|
|
@param PrevTd Previous UHCI_TD_SW to be linked to.
|
|
@param ThisTd TD to link.
|
|
|
|
**/
|
|
VOID
|
|
UhciAppendTd (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN UHCI_TD_SW *PrevTd,
|
|
IN UHCI_TD_SW *ThisTd
|
|
)
|
|
{
|
|
EFI_PHYSICAL_ADDRESS PhyAddr;
|
|
|
|
PhyAddr = UsbHcGetPciAddressForHostMem (Uhc->MemPool, ThisTd, sizeof (UHCI_TD_HW));
|
|
|
|
ASSERT ((PrevTd != NULL) && (ThisTd != NULL));
|
|
|
|
PrevTd->TdHw.NextLink = TD_LINK (PhyAddr, TRUE, FALSE);
|
|
PrevTd->NextTd = (VOID *) ThisTd;
|
|
}
|
|
|
|
|
|
/**
|
|
Delete a list of TDs.
|
|
|
|
@param Uhc The UHCI device.
|
|
@param FirstTd TD link list head.
|
|
|
|
@return None.
|
|
|
|
**/
|
|
VOID
|
|
UhciDestoryTds (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN UHCI_TD_SW *FirstTd
|
|
)
|
|
{
|
|
UHCI_TD_SW *NextTd;
|
|
UHCI_TD_SW *ThisTd;
|
|
|
|
NextTd = FirstTd;
|
|
|
|
while (NextTd != NULL) {
|
|
ThisTd = NextTd;
|
|
NextTd = ThisTd->NextTd;
|
|
UsbHcFreeMem (Uhc->MemPool, ThisTd, sizeof (UHCI_TD_SW));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
Create an initialize a new queue head.
|
|
|
|
@param Uhc The UHCI device.
|
|
@param Interval The polling interval for the queue.
|
|
|
|
@return The newly created queue header.
|
|
|
|
**/
|
|
UHCI_QH_SW *
|
|
UhciCreateQh (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN UINTN Interval
|
|
)
|
|
{
|
|
UHCI_QH_SW *Qh;
|
|
|
|
Qh = UsbHcAllocateMem (Uhc->MemPool, sizeof (UHCI_QH_SW));
|
|
|
|
if (Qh == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Qh->QhHw.HorizonLink = QH_HLINK (NULL, TRUE);
|
|
Qh->QhHw.VerticalLink = QH_VLINK (NULL, TRUE);
|
|
Qh->Interval = UhciConvertPollRate(Interval);
|
|
Qh->TDs = NULL;
|
|
Qh->NextQh = NULL;
|
|
|
|
return Qh;
|
|
}
|
|
|
|
|
|
/**
|
|
Create and intialize a TD.
|
|
|
|
@param Uhc The UHCI device.
|
|
|
|
@return The newly allocated and initialized TD.
|
|
|
|
**/
|
|
UHCI_TD_SW *
|
|
UhciCreateTd (
|
|
IN USB_HC_DEV *Uhc
|
|
)
|
|
{
|
|
UHCI_TD_SW *Td;
|
|
|
|
Td = UsbHcAllocateMem (Uhc->MemPool, sizeof (UHCI_TD_SW));
|
|
if (Td == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Td->TdHw.NextLink = TD_LINK (NULL, FALSE, TRUE);
|
|
Td->NextTd = NULL;
|
|
Td->Data = NULL;
|
|
Td->DataLen = 0;
|
|
|
|
return Td;
|
|
}
|
|
|
|
|
|
/**
|
|
Create and initialize a TD for Setup Stage of a control transfer.
|
|
|
|
@param Uhc The UHCI device.
|
|
@param DevAddr Device address.
|
|
@param Request A pointer to cpu memory address of Device request.
|
|
@param RequestPhy A pointer to pci memory address of Device request.
|
|
@param IsLow Full speed or low speed.
|
|
|
|
@return The created setup Td Pointer.
|
|
|
|
**/
|
|
UHCI_TD_SW *
|
|
UhciCreateSetupTd (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN UINT8 DevAddr,
|
|
IN UINT8 *Request,
|
|
IN UINT8 *RequestPhy,
|
|
IN BOOLEAN IsLow
|
|
)
|
|
{
|
|
UHCI_TD_SW *Td;
|
|
|
|
Td = UhciCreateTd (Uhc);
|
|
|
|
if (Td == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Td->TdHw.NextLink = TD_LINK (NULL, TRUE, TRUE);
|
|
Td->TdHw.ShortPacket = FALSE;
|
|
Td->TdHw.IsIsoch = FALSE;
|
|
Td->TdHw.IntOnCpl = FALSE;
|
|
Td->TdHw.ErrorCount = 0x03;
|
|
Td->TdHw.Status |= USBTD_ACTIVE;
|
|
Td->TdHw.DataToggle = 0;
|
|
Td->TdHw.EndPoint = 0;
|
|
Td->TdHw.LowSpeed = IsLow ? 1 : 0;
|
|
Td->TdHw.DeviceAddr = DevAddr & 0x7F;
|
|
Td->TdHw.MaxPacketLen = (UINT32) (sizeof (EFI_USB_DEVICE_REQUEST) - 1);
|
|
Td->TdHw.PidCode = SETUP_PACKET_ID;
|
|
Td->TdHw.DataBuffer = (UINT32) (UINTN) RequestPhy;
|
|
|
|
Td->Data = Request;
|
|
Td->DataLen = (UINT16) sizeof (EFI_USB_DEVICE_REQUEST);
|
|
|
|
return Td;
|
|
}
|
|
|
|
|
|
/**
|
|
Create a TD for data.
|
|
|
|
@param Uhc The UHCI device.
|
|
@param DevAddr Device address.
|
|
@param Endpoint Endpoint number.
|
|
@param DataPtr A pointer to cpu memory address of Data buffer.
|
|
@param DataPhyPtr A pointer to pci memory address of Data buffer.
|
|
@param Len Data length.
|
|
@param PktId Packet ID.
|
|
@param Toggle Data toggle value.
|
|
@param IsLow Full speed or low speed.
|
|
|
|
@return Data Td pointer if success, otherwise NULL.
|
|
|
|
**/
|
|
UHCI_TD_SW *
|
|
UhciCreateDataTd (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN UINT8 DevAddr,
|
|
IN UINT8 Endpoint,
|
|
IN UINT8 *DataPtr,
|
|
IN UINT8 *DataPhyPtr,
|
|
IN UINTN Len,
|
|
IN UINT8 PktId,
|
|
IN UINT8 Toggle,
|
|
IN BOOLEAN IsLow
|
|
)
|
|
{
|
|
UHCI_TD_SW *Td;
|
|
|
|
//
|
|
// Code as length - 1, and the max valid length is 0x500
|
|
//
|
|
ASSERT (Len <= 0x500);
|
|
|
|
Td = UhciCreateTd (Uhc);
|
|
|
|
if (Td == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Td->TdHw.NextLink = TD_LINK (NULL, TRUE, TRUE);
|
|
Td->TdHw.ShortPacket = FALSE;
|
|
Td->TdHw.IsIsoch = FALSE;
|
|
Td->TdHw.IntOnCpl = FALSE;
|
|
Td->TdHw.ErrorCount = 0x03;
|
|
Td->TdHw.Status = USBTD_ACTIVE;
|
|
Td->TdHw.LowSpeed = IsLow ? 1 : 0;
|
|
Td->TdHw.DataToggle = Toggle & 0x01;
|
|
Td->TdHw.EndPoint = Endpoint & 0x0F;
|
|
Td->TdHw.DeviceAddr = DevAddr & 0x7F;
|
|
Td->TdHw.MaxPacketLen = (UINT32) (Len - 1);
|
|
Td->TdHw.PidCode = (UINT8) PktId;
|
|
Td->TdHw.DataBuffer = (UINT32) (UINTN) DataPhyPtr;
|
|
|
|
Td->Data = DataPtr;
|
|
Td->DataLen = (UINT16) Len;
|
|
|
|
return Td;
|
|
}
|
|
|
|
|
|
/**
|
|
Create TD for the Status Stage of control transfer.
|
|
|
|
@param Uhc The UHCI device.
|
|
@param DevAddr Device address.
|
|
@param PktId Packet ID.
|
|
@param IsLow Full speed or low speed.
|
|
|
|
@return Status Td Pointer.
|
|
|
|
**/
|
|
UHCI_TD_SW *
|
|
UhciCreateStatusTd (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN UINT8 DevAddr,
|
|
IN UINT8 PktId,
|
|
IN BOOLEAN IsLow
|
|
)
|
|
{
|
|
UHCI_TD_SW *Td;
|
|
|
|
Td = UhciCreateTd (Uhc);
|
|
|
|
if (Td == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
Td->TdHw.NextLink = TD_LINK (NULL, TRUE, TRUE);
|
|
Td->TdHw.ShortPacket = FALSE;
|
|
Td->TdHw.IsIsoch = FALSE;
|
|
Td->TdHw.IntOnCpl = FALSE;
|
|
Td->TdHw.ErrorCount = 0x03;
|
|
Td->TdHw.Status |= USBTD_ACTIVE;
|
|
Td->TdHw.MaxPacketLen = 0x7FF; //0x7FF: there is no data (refer to UHCI spec)
|
|
Td->TdHw.DataToggle = 1;
|
|
Td->TdHw.EndPoint = 0;
|
|
Td->TdHw.LowSpeed = IsLow ? 1 : 0;
|
|
Td->TdHw.DeviceAddr = DevAddr & 0x7F;
|
|
Td->TdHw.PidCode = (UINT8) PktId;
|
|
Td->TdHw.DataBuffer = (UINT32) (UINTN) NULL;
|
|
|
|
Td->Data = NULL;
|
|
Td->DataLen = 0;
|
|
|
|
return Td;
|
|
}
|
|
|
|
|
|
/**
|
|
Create Tds list for Control Transfer.
|
|
|
|
@param Uhc The UHCI device.
|
|
@param DeviceAddr The device address.
|
|
@param DataPktId Packet Identification of Data Tds.
|
|
@param Request A pointer to cpu memory address of request structure buffer to transfer.
|
|
@param RequestPhy A pointer to pci memory address of request structure buffer to transfer.
|
|
@param Data A pointer to cpu memory address of user data buffer to transfer.
|
|
@param DataPhy A pointer to pci memory address of user data buffer to transfer.
|
|
@param DataLen Length of user data to transfer.
|
|
@param MaxPacket Maximum packet size for control transfer.
|
|
@param IsLow Full speed or low speed.
|
|
|
|
@return The Td list head for the control transfer.
|
|
|
|
**/
|
|
UHCI_TD_SW *
|
|
UhciCreateCtrlTds (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN UINT8 DeviceAddr,
|
|
IN UINT8 DataPktId,
|
|
IN UINT8 *Request,
|
|
IN UINT8 *RequestPhy,
|
|
IN UINT8 *Data,
|
|
IN UINT8 *DataPhy,
|
|
IN UINTN DataLen,
|
|
IN UINT8 MaxPacket,
|
|
IN BOOLEAN IsLow
|
|
)
|
|
{
|
|
UHCI_TD_SW *SetupTd;
|
|
UHCI_TD_SW *FirstDataTd;
|
|
UHCI_TD_SW *DataTd;
|
|
UHCI_TD_SW *PrevDataTd;
|
|
UHCI_TD_SW *StatusTd;
|
|
UINT8 DataToggle;
|
|
UINT8 StatusPktId;
|
|
UINTN ThisTdLen;
|
|
|
|
|
|
DataTd = NULL;
|
|
SetupTd = NULL;
|
|
FirstDataTd = NULL;
|
|
PrevDataTd = NULL;
|
|
StatusTd = NULL;
|
|
|
|
//
|
|
// Create setup packets for the transfer
|
|
//
|
|
SetupTd = UhciCreateSetupTd (Uhc, DeviceAddr, Request, RequestPhy, IsLow);
|
|
|
|
if (SetupTd == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
//
|
|
// Create data packets for the transfer
|
|
//
|
|
DataToggle = 1;
|
|
|
|
while (DataLen > 0) {
|
|
//
|
|
// PktSize is the data load size in each Td.
|
|
//
|
|
ThisTdLen = (DataLen > MaxPacket ? MaxPacket : DataLen);
|
|
|
|
DataTd = UhciCreateDataTd (
|
|
Uhc,
|
|
DeviceAddr,
|
|
0,
|
|
Data, //cpu memory address
|
|
DataPhy, //Pci memory address
|
|
ThisTdLen,
|
|
DataPktId,
|
|
DataToggle,
|
|
IsLow
|
|
);
|
|
|
|
if (DataTd == NULL) {
|
|
goto FREE_TD;
|
|
}
|
|
|
|
if (FirstDataTd == NULL) {
|
|
FirstDataTd = DataTd;
|
|
FirstDataTd->NextTd = NULL;
|
|
} else {
|
|
UhciAppendTd (Uhc, PrevDataTd, DataTd);
|
|
}
|
|
|
|
DataToggle ^= 1;
|
|
PrevDataTd = DataTd;
|
|
Data += ThisTdLen;
|
|
DataPhy += ThisTdLen;
|
|
DataLen -= ThisTdLen;
|
|
}
|
|
|
|
//
|
|
// Status packet is on the opposite direction to data packets
|
|
//
|
|
if (OUTPUT_PACKET_ID == DataPktId) {
|
|
StatusPktId = INPUT_PACKET_ID;
|
|
} else {
|
|
StatusPktId = OUTPUT_PACKET_ID;
|
|
}
|
|
|
|
StatusTd = UhciCreateStatusTd (Uhc, DeviceAddr, StatusPktId, IsLow);
|
|
|
|
if (StatusTd == NULL) {
|
|
goto FREE_TD;
|
|
}
|
|
|
|
//
|
|
// Link setup Td -> data Tds -> status Td together
|
|
//
|
|
if (FirstDataTd != NULL) {
|
|
UhciAppendTd (Uhc, SetupTd, FirstDataTd);
|
|
UhciAppendTd (Uhc, PrevDataTd, StatusTd);
|
|
} else {
|
|
UhciAppendTd (Uhc, SetupTd, StatusTd);
|
|
}
|
|
|
|
return SetupTd;
|
|
|
|
FREE_TD:
|
|
if (SetupTd != NULL) {
|
|
UhciDestoryTds (Uhc, SetupTd);
|
|
}
|
|
|
|
if (FirstDataTd != NULL) {
|
|
UhciDestoryTds (Uhc, FirstDataTd);
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/**
|
|
Create Tds list for Bulk/Interrupt Transfer.
|
|
|
|
@param Uhc USB_HC_DEV.
|
|
@param DevAddr Address of Device.
|
|
@param EndPoint Endpoint Number.
|
|
@param PktId Packet Identification of Data Tds.
|
|
@param Data A pointer to cpu memory address of user data buffer to transfer.
|
|
@param DataPhy A pointer to pci memory address of user data buffer to transfer.
|
|
@param DataLen Length of user data to transfer.
|
|
@param DataToggle Data Toggle Pointer.
|
|
@param MaxPacket Maximum packet size for Bulk/Interrupt transfer.
|
|
@param IsLow Is Low Speed Device.
|
|
|
|
@return The Tds list head for the bulk transfer.
|
|
|
|
**/
|
|
UHCI_TD_SW *
|
|
UhciCreateBulkOrIntTds (
|
|
IN USB_HC_DEV *Uhc,
|
|
IN UINT8 DevAddr,
|
|
IN UINT8 EndPoint,
|
|
IN UINT8 PktId,
|
|
IN UINT8 *Data,
|
|
IN UINT8 *DataPhy,
|
|
IN UINTN DataLen,
|
|
IN OUT UINT8 *DataToggle,
|
|
IN UINT8 MaxPacket,
|
|
IN BOOLEAN IsLow
|
|
)
|
|
{
|
|
UHCI_TD_SW *DataTd;
|
|
UHCI_TD_SW *FirstDataTd;
|
|
UHCI_TD_SW *PrevDataTd;
|
|
UINTN ThisTdLen;
|
|
|
|
DataTd = NULL;
|
|
FirstDataTd = NULL;
|
|
PrevDataTd = NULL;
|
|
|
|
//
|
|
// Create data packets for the transfer
|
|
//
|
|
while (DataLen > 0) {
|
|
//
|
|
// PktSize is the data load size that each Td.
|
|
//
|
|
ThisTdLen = DataLen;
|
|
|
|
if (DataLen > MaxPacket) {
|
|
ThisTdLen = MaxPacket;
|
|
}
|
|
|
|
DataTd = UhciCreateDataTd (
|
|
Uhc,
|
|
DevAddr,
|
|
EndPoint,
|
|
Data,
|
|
DataPhy,
|
|
ThisTdLen,
|
|
PktId,
|
|
*DataToggle,
|
|
IsLow
|
|
);
|
|
|
|
if (DataTd == NULL) {
|
|
goto FREE_TD;
|
|
}
|
|
|
|
if (PktId == INPUT_PACKET_ID) {
|
|
DataTd->TdHw.ShortPacket = TRUE;
|
|
}
|
|
|
|
if (FirstDataTd == NULL) {
|
|
FirstDataTd = DataTd;
|
|
FirstDataTd->NextTd = NULL;
|
|
} else {
|
|
UhciAppendTd (Uhc, PrevDataTd, DataTd);
|
|
}
|
|
|
|
*DataToggle ^= 1;
|
|
PrevDataTd = DataTd;
|
|
Data += ThisTdLen;
|
|
DataPhy += ThisTdLen;
|
|
DataLen -= ThisTdLen;
|
|
}
|
|
|
|
return FirstDataTd;
|
|
|
|
FREE_TD:
|
|
if (FirstDataTd != NULL) {
|
|
UhciDestoryTds (Uhc, FirstDataTd);
|
|
}
|
|
|
|
return NULL;
|
|
}
|