windows-nt/Source/XPSP1/NT/drivers/wdm/usb/hcd/openhci/ohciurb.c

2029 lines
58 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/*++
Copyright (c) 1995 Microsoft Corporation
Module Name:
ohciurb.c
Abstract:
The module manages transactions on the USB.
Environment:
kernel mode only
Notes:
Revision History:
02-07-96: created jfuller
03-05-96: in work kenray
--*/
#include "openhci.h"
/*
Private functions to this module
*/
NTSTATUS
OpenHCI_OpenEndpoint(
IN PDEVICE_OBJECT,
IN PIRP,
PHCD_DEVICE_DATA,
PHCD_URB);
NTSTATUS
OpenHCI_CloseEndpoint(
IN PDEVICE_OBJECT,
IN PIRP,
PHCD_DEVICE_DATA,
PHCD_URB);
VOID
OpenHCI_CompleteIrp(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN NTSTATUS ntStatus
)
/*++
Routine Description:
Completes an I/O Request
Arguments:
DeviceObject - pointer to a device object
Irp - pointer to an I/O Request Packet to complete
ntStatus - status code to set int the IRP when completed
Return Value:
--*/
{
PHCD_DEVICE_DATA DeviceData;
DeviceData = DeviceObject->DeviceExtension;
Irp->IoStatus.Status = ntStatus;
Irp->IoStatus.Information = 0;
//
// call IoCompleteRequest thru USBD to give it a chance to do
// cleanup and error mapping
//
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_CALL_TRACE,
("'Completing IRP %x (%x)\n", Irp, ntStatus));
IRP_OUT(Irp);
LOGENTRY(G, 'Cirp', Irp, 0, ntStatus);
USBD_CompleteRequest(Irp, IO_NO_INCREMENT);
}
NTSTATUS
OpenHCI_URB_Dispatch(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp
)
/*++
Routine Description:
Process URBs from the dispatch routine.
Arguments:
DeviceObject - pointer to a device object
Irp - pointer to an I/O Request Packet
Return Value:
--*/
{
PHCD_URB urb;
NTSTATUS ntStatus = STATUS_SUCCESS;
PHCD_DEVICE_DATA DeviceData;
PHCD_ENDPOINT endpoint;
struct _URB_HCD_ENDPOINT_STATE *state;
PHC_OPERATIONAL_REGISTER HC;
DeviceData = (PHCD_DEVICE_DATA) DeviceObject->DeviceExtension;
OpenHCI_KdPrintDD(DeviceData,
OHCI_DBG_CALL_TRACE, ("'enter URB_Dispatch \n"));
HC = DeviceData->HC;
urb = (PHCD_URB) URB_FROM_IRP(Irp);
switch (urb->UrbHeader.Function) {
//
// Open Endpoint and Close Endpoint IRPs are serialized
// within USBD so we can execute them now.
//
case URB_FUNCTION_HCD_OPEN_ENDPOINT:
ntStatus = OpenHCI_OpenEndpoint(DeviceObject, Irp, DeviceData, urb);
OpenHCI_CompleteIrp(DeviceObject, Irp, ntStatus);
break;
case URB_FUNCTION_HCD_CLOSE_ENDPOINT:
ntStatus = OpenHCI_CloseEndpoint(DeviceObject, Irp, DeviceData, urb);
OpenHCI_CompleteIrp(DeviceObject, Irp, ntStatus);
break;
case URB_FUNCTION_CONTROL_TRANSFER:
case URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER:
case URB_FUNCTION_ISOCH_TRANSFER:
endpoint = urb->HcdUrbCommonTransfer.hca.HcdEndpoint;
ntStatus = OpenHCI_QueueTransfer(DeviceObject, Irp);
if (ntStatus != STATUS_PENDING) {
OpenHCI_CompleteIrp(DeviceObject, Irp, ntStatus);
}
break;
case URB_FUNCTION_GET_CURRENT_FRAME_NUMBER:
urb->UrbGetCurrentFrameNumber.FrameNumber
= Get32BitFrameNumber(DeviceData);
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_CALL_INFO,
("'Get32BitFrameNumber: %x",
Get32BitFrameNumber(DeviceData)));
urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
OpenHCI_CompleteIrp(DeviceObject, Irp, ntStatus);
break;
case URB_FUNCTION_SET_FRAME_LENGTH:
{
HC_FM_INTERVAL interval;
//get the current value
interval.ul =
READ_REGISTER_ULONG(&HC->HcFmInterval.ul);
interval.FrameInterval +=
(CHAR) urb->UrbSetFrameLength.FrameLengthDelta;
WRITE_REGISTER_ULONG(&HC->HcFmInterval.ul,
interval.ul);
urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
OpenHCI_CompleteIrp(DeviceObject, Irp, ntStatus);
}
break;
case URB_FUNCTION_GET_FRAME_LENGTH:
{
HC_FM_INTERVAL interval;
//get the current value
interval.ul =
READ_REGISTER_ULONG(&HC->HcFmInterval.ul);
urb->UrbGetFrameLength.FrameNumber =
Get32BitFrameNumber(DeviceData);
urb->UrbGetFrameLength.FrameLength =
interval.FrameInterval;
urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
OpenHCI_CompleteIrp(DeviceObject, Irp, ntStatus);
}
break;
case URB_FUNCTION_HCD_GET_ENDPOINT_STATE:
{
KIRQL irql;
BOOLEAN queuedTransfers, activeTransfers;
state = &urb->HcdUrbEndpointState;
endpoint = state->HcdEndpoint;
LOGENTRY(G, 'gEPS', Irp, endpoint, 0);
if (endpoint == ZERO_LOAD_ENDPOINT(DeviceData)) {
state->HcdEndpointState = 0;
} else {
ASSERT_ENDPOINT(endpoint);
OpenHCI_LockAndCheckEndpoint(endpoint,
&queuedTransfers,
&activeTransfers,
&irql);
if (endpoint->EpFlags & EP_ROOT_HUB) {
state->HcdEndpointState =
(queuedTransfers | activeTransfers)
? HCD_ENDPOINT_TRANSFERS_QUEUED : 0;
} else {
state->HcdEndpointState =
((endpoint->HcdED->HcED.HeadP & HcEDHeadP_HALT)
? HCD_ENDPOINT_HALTED : 0)
| ((queuedTransfers | activeTransfers)
? HCD_ENDPOINT_TRANSFERS_QUEUED : 0);
}
OpenHCI_UnlockEndpoint(endpoint,
irql);
}
urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
LOGENTRY(G, 'GETs', Irp, endpoint, state->HcdEndpointState);
OpenHCI_CompleteIrp(DeviceObject, Irp, ntStatus);
}
break;
case URB_FUNCTION_HCD_SET_ENDPOINT_STATE:
{
PHC_ENDPOINT_DESCRIPTOR hcED;
state = &urb->HcdUrbEndpointState;
endpoint = state->HcdEndpoint;
ASSERT_ENDPOINT(endpoint);
hcED = &endpoint->HcdED->HcED;
LOGENTRY(G, 'SETs', Irp, endpoint, state->HcdEndpointState);
// reset some endpoint flags
SET_EPFLAG(endpoint, EP_VIRGIN);
// need to reset data toggle on clear halt
if (state->HcdEndpointState & HCD_ENDPOINT_RESET_DATA_TOGGLE) {
//OHCI_ASSERT(hcED->sKip ||
// (hcED->HeadP & HcEDHeadP_HALT));
hcED->HeadP = (endpoint->HcdHeadP->PhysicalAddress
& ~HcEDHeadP_CARRY);
}
if (!(state->HcdEndpointState & HCD_ENDPOINT_HALTED)) {
LOGENTRY(G, 'CLRh', Irp, endpoint, state->HcdEndpointState);
if (hcED->HeadP & HcEDHeadP_HALT) {
// If the endpoint is indeed halted than the hardware will
// not be touching the ED. So we should be able to set this
// flag with impunity.
hcED->HeadP &= ~HcEDHeadP_HALT;
LOGENTRY(G, 'HLTn', Irp, endpoint, state->HcdEndpointState);
}
ENABLE_LIST(DeviceData->HC, endpoint);
}
}
urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
OpenHCI_CompleteIrp(DeviceObject, Irp, ntStatus);
break;
case URB_FUNCTION_HCD_ABORT_ENDPOINT:
endpoint = urb->HcdUrbAbortEndpoint.HcdEndpoint;
LOGENTRY(G, 'Abrt', Irp, endpoint, 0);
if (ZERO_LOAD_ENDPOINT(DeviceData) == endpoint) {
ntStatus = STATUS_SUCCESS;
} else {
KIRQL oldIrq;
KeAcquireSpinLock(&DeviceData->PausedSpin, &oldIrq);
#if DBG
if (endpoint->EpFlags & EP_ROOT_HUB) {
OHCI_ASSERT(endpoint->AbortIrp == NULL);
}
#endif
if (endpoint->AbortIrp != NULL) {
// if we get an abort while we still have an abort irp
// pending then the driver has a bug, we will just fail
// the request
TRAP();
ntStatus = STATUS_UNSUCCESSFUL;
KeReleaseSpinLock(&DeviceData->PausedSpin, oldIrq);
OpenHCI_CompleteIrp(DeviceObject, Irp, ntStatus);
} else {
KeReleaseSpinLock(&DeviceData->PausedSpin, oldIrq);
ntStatus = OpenHCI_AbortEndpoint(DeviceObject,
Irp,
DeviceData,
urb);
}
}
break;
default:
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_CALL_ERROR,
("'OpenHCI_URB_Dispatch -- invalid URB function (%x)\n",
urb->UrbHeader.Function));
urb->UrbHeader.Status = USBD_STATUS_INVALID_URB_FUNCTION;
OpenHCI_CompleteIrp(DeviceObject, Irp, ntStatus);
}
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_CALL_TRACE,
("'exit OpenHCI_URB_Dispatch (%x)\n", ntStatus));
return ntStatus;
}
NTSTATUS
OpenHCI_GrowDescriptorPool (
IN PHCD_DEVICE_DATA DeviceData,
IN ULONG ReserveLength,
OUT PCHAR *VirtAddr OPTIONAL,
OUT PHYSICAL_ADDRESS *PhysAddr OPTIONAL
)
/*++
Routine Description:
Reserve Transfer/Endpoint Descriptors.
This function allocates a block of common buffer memory for
Transfer and Endpoint Descriptors and puts it on a tracking
list in the device extension.
Arguments:
DeviceData - pointer to a device extension
ReserveLength - amount of space to reserve at the beginning
of the common buffer
VirtAddr - virtual address of space reserved at the beginning
of the common buffer
PhysAddr - physical address of space reserved at the beginning
of the common buffer
Return Value:
NT Status code
--*/
{
ULONG allocLength;
PCHAR pageVirt;
PHYSICAL_ADDRESS pagePhys;
PPAGE_LIST_ENTRY pageList;
PHCD_TRANSFER_DESCRIPTOR td;
// Assert that sizeof(HCD_TRANSFER_DESCRIPTOR) is a power of 2
//
C_ASSERT((sizeof(HCD_TRANSFER_DESCRIPTOR) &
sizeof(HCD_TRANSFER_DESCRIPTOR) - 1) == 0);
// Round up ReserveLength to the next multiple of the
// sizeof(HCD_TRANSFER_DESCRIPTOR).
//
ReserveLength += sizeof(HCD_TRANSFER_DESCRIPTOR) - 1;
ReserveLength &= ~(sizeof(HCD_TRANSFER_DESCRIPTOR) - 1);
// Round up allocLength to the next multiple of PAGE_SIZE
//
allocLength = ReserveLength + PAGE_SIZE;
allocLength &= ~(PAGE_SIZE - 1);
// Now allocate the common buffer
//
pageVirt = HalAllocateCommonBuffer(DeviceData->AdapterObject,
allocLength,
&pagePhys,
CacheCommon);
if (pageVirt == NULL)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
// Zero initialize the whole page
//
RtlZeroMemory(pageVirt, allocLength);
// Allocate a PAGE_LIST_ENTRY to track the page of commom buffer.
//
pageList = ExAllocatePoolWithTag(NonPagedPool,
sizeof(PAGE_LIST_ENTRY),
OpenHCI_TAG);
if (pageList == NULL)
{
HalFreeCommonBuffer(DeviceData->AdapterObject,
allocLength,
pagePhys,
pageVirt,
CacheCommon);
return STATUS_INSUFFICIENT_RESOURCES;
}
LOGENTRY(G, 'GROW', DeviceData, pageList, pagePhys.LowPart);
// Initialize the PAGE_LIST_ENTRY to track the page of commom buffer.
//
pageList->BufferSize = allocLength;
pageList->VirtAddr = pageVirt;
pageList->PhysAddr = pagePhys;
pageList->FirstTDVirt = (PHCD_TRANSFER_DESCRIPTOR)(pageVirt + ReserveLength);
pageList->LastTDVirt = (PHCD_TRANSFER_DESCRIPTOR)(pageVirt + allocLength) - 1;
pageList->FirstTDPhys = pagePhys;
pageList->FirstTDPhys.LowPart += ReserveLength;
pageList->LastTDPhys = pagePhys;
pageList->LastTDPhys.LowPart += allocLength - sizeof(PHCD_TRANSFER_DESCRIPTOR);;
// Add the PAGE_LIST_ENTRY to the device extension
//
ExInterlockedPushEntryList((PSINGLE_LIST_ENTRY)&DeviceData->PageList,
(PSINGLE_LIST_ENTRY)pageList,
&DeviceData->PageListSpin);
// Add all of the TDs in the page of common buffer to the
// Free Descriptor list.
//
pagePhys = pageList->FirstTDPhys;
for (td = pageList->FirstTDVirt; td <= pageList->LastTDVirt; td++)
{
// Initialize the TD PhysicalAddress field to point back to the TD
//
td->PhysicalAddress = pagePhys.LowPart;
pagePhys.LowPart += sizeof(HCD_TRANSFER_DESCRIPTOR);
ExInterlockedPushEntryList(&DeviceData->FreeDescriptorList,
(PSINGLE_LIST_ENTRY)td,
&DeviceData->DescriptorsSpin);
InterlockedIncrement(&DeviceData->FreeDescriptorCount);
}
// Return pointers to the reserved space if desired
//
if (VirtAddr)
{
*VirtAddr = pageList->VirtAddr;
}
if (PhysAddr)
{
*PhysAddr = pageList->PhysAddr;
}
return STATUS_SUCCESS;
}
NTSTATUS
OpenHCI_ReserveDescriptors(
IN PHCD_DEVICE_DATA DeviceData,
IN ULONG DescriptorCount
)
/*++
Routine Description:
Reserve Transfer/Endpoint Descriptors.
NOTE:
This routine is called only by openendpoint so it will never be
rentered.
Arguments:
DeviceData - pointer to a device extension
DescriptorCount - number of descriptors to reserve
Return Value:
NT Status code
--*/
{
NTSTATUS ntStatus = STATUS_SUCCESS;
while (DeviceData->FreeDescriptorCount < DescriptorCount &&
NT_SUCCESS(ntStatus))
{
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_CALL_TRACE,
("'grow pool by one page\n"));
ntStatus = OpenHCI_GrowDescriptorPool(DeviceData,
0,
NULL,
NULL);
}
if (NT_SUCCESS(ntStatus))
{
DeviceData->FreeDescriptorCount -= DescriptorCount;
}
LOGENTRY(G, 'rsDS',
DescriptorCount, DeviceData->FreeDescriptorCount, ntStatus);
return ntStatus;
}
NTSTATUS
OpenHCI_UnReserveDescriptors(
IN PHCD_DEVICE_DATA DeviceData,
IN ULONG DescriptorCount
)
/*++
Routine Description:
Free reserved Transfer/Endpoint Descriptors.
NOTE:
This routine is called only by closeendpoint so it will never be
rentered.
Arguments:
DeviceData - pointer to a device extension
DescriptorCount - number of descriptors to reserve
Return Value:
NT Status code
--*/
{
DeviceData->FreeDescriptorCount += DescriptorCount;
LOGENTRY(G, 'urDS', DescriptorCount, DeviceData->FreeDescriptorCount, 0);
return STATUS_SUCCESS;
}
PHCD_TRANSFER_DESCRIPTOR
OpenHCI_Alloc_HcdTD(
PHCD_DEVICE_DATA DeviceData
)
/*++
Routine Description:
allocate a reserved desriptor, this routine can be called at
IRQL >= DISPATCH_LEVEL
Arguments:
DeviceData - pointer to a device extension
Return Value:
NT Status code
--*/
{
PHCD_TRANSFER_DESCRIPTOR td;
td = (PHCD_TRANSFER_DESCRIPTOR)
ExInterlockedPopEntryList(&DeviceData->FreeDescriptorList,
&DeviceData->DescriptorsSpin);
LOGENTRY(G, 'alTD', &DeviceData->FreeDescriptorList, td, DeviceData->FreeDescriptorCount);
OHCI_ASSERT(td != NULL);
OHCI_ASSERT(td->Flags == 0);
OHCI_ASSERT((td->PhysicalAddress & (PAGE_SIZE-1)) ==
((ULONG_PTR)td & (PAGE_SIZE-1)));
// Initialize the TD NextTD pointer to a non-zero value before setting
// the TD_FLAG_INUSE flag. The only time a TD NextTD pointer should be
// zero when the TD_FLAG_INUSE flag is set is when the TD is the tail
// end of a done list.
//
td->HcTD.NextTD = 0x2BAD2BAD;
td->Flags = TD_FLAG_INUSE;
//
// if we fail to get a td we have a bug since we always reserve
// enough for the worst case scenario when an endpoint is opened
//
return td;
}
VOID
OpenHCI_Free_HcdTD(
PHCD_DEVICE_DATA DeviceData,
PHCD_TRANSFER_DESCRIPTOR Td
)
{
LOGENTRY(G, 'frTD', &DeviceData->FreeDescriptorList, Td, DeviceData->FreeDescriptorCount);
OHCI_ASSERT((Td->PhysicalAddress & (PAGE_SIZE-1)) ==
((ULONG_PTR)Td & (PAGE_SIZE-1)));
OHCI_ASSERT(Td->Flags == TD_FLAG_INUSE);
Td->Flags = 0;
ExInterlockedPushEntryList(&DeviceData->FreeDescriptorList,
(PSINGLE_LIST_ENTRY) Td,
&DeviceData->DescriptorsSpin);
}
PHCD_ENDPOINT_DESCRIPTOR
OpenHCI_Alloc_HcdED(
PHCD_DEVICE_DATA DeviceData
)
/*++
Routine Description:
allocate a reserved desriptor, this routine can be called at
IRQL >= DISPATCH_LEVEL
Arguments:
DeviceData - pointer to a device extension
Return Value:
NT Status code
--*/
{
PHCD_ENDPOINT_DESCRIPTOR ed;
ed = (PHCD_ENDPOINT_DESCRIPTOR)
ExInterlockedPopEntryList(&DeviceData->FreeDescriptorList,
&DeviceData->DescriptorsSpin);
LOGENTRY(G, 'alED', &DeviceData->FreeDescriptorList, ed, DeviceData->FreeDescriptorCount);
OHCI_ASSERT(ed != NULL);
OHCI_ASSERT(ed->Flags == 0);
OHCI_ASSERT((ed->PhysicalAddress & (PAGE_SIZE-1)) ==
((ULONG_PTR)ed & (PAGE_SIZE-1)));
ed->Flags = TD_FLAG_INUSE | TD_FLAG_IS_ED;
//
// if we fail to get a td we have a bug since we always reserve
// enough for the worst case scenario when an endpoint is opened
//
return ed;
}
PHCD_TRANSFER_DESCRIPTOR
OpenHCI_LogDesc_to_PhyDesc(
PHCD_DEVICE_DATA DeviceData,
ULONG LogDesc
)
/*++
Routine Description:
This routine scans the list of pages allocated by
HalAllocateCommonBuffer into the list DeviceData->PageList
for the storage of Descriptors.
The first entries describe the virtual and logical
addresses of those pages.
Arguments:
LogDesc is the logical address of a HcTD structure in memory.
Returned Value:
The virtual address of the HCD_Descriptor the (logical) HC_descriptor.
Note that the virtual address of the HC_TRANSFER_DESCRIPTOR
is the same as HCD_TRANSFER_DESCRIPTOR as well as
the HC_ENDPOINT_DESCRIPTOR the same as HCD_TRANSFER_DESCRIPTOR.
If no translation is found this will return NULL.
There are concurrent writes to DeviceData->PageList, but throughout
the life of DeviceData, only new pages will be pushed onto the list.
If these pages are added to the list while this function is running,
these pages cannot contain the logical address for which we are
searching.
--*/
{
PPAGE_LIST_ENTRY PageList;
PHCD_TRANSFER_DESCRIPTOR td;
if (LogDesc & (sizeof(HCD_TRANSFER_DESCRIPTOR)-1))
{
// The address is not properly aligned to be a TD.
// Something bad has happened. Try our best not to
// fault processing a bogus TD.
//
LOGENTRY(G, 'Phy1', DeviceData, LogDesc, 0);
//TEST_TRAP();
return NULL;
}
PageList = (PPAGE_LIST_ENTRY)DeviceData->PageList;
while (PageList)
{
if (LogDesc >= PageList->FirstTDPhys.LowPart &&
LogDesc <= PageList->LastTDPhys.LowPart)
{
td = (PHCD_TRANSFER_DESCRIPTOR)((PCHAR)PageList->FirstTDVirt +
LogDesc - PageList->FirstTDPhys.LowPart);
if (td->Flags == TD_FLAG_INUSE)
{
// Appears to be a valid TD
//
return td;
}
else
{
// The TD is not marked as an in-use TD. Something
// bad has happened. Try our best not to fault
// processing a bogus TD.
//
LOGENTRY(G, 'Phy2', DeviceData, LogDesc, 0);
//TEST_TRAP();
return NULL;
}
}
PageList = PageList->NextPage;
}
return NULL;
}
ULONG
OpenHCI_CheckBandwidth(
IN PHCD_DEVICE_DATA DeviceData,
IN UCHAR List,
OUT PCHAR BestList
)
/*++
Routine Description:
This routine scans all the scheduling lists of frequency
determined by the base List passed in and returns the worst
bandwidth found (i.e., max in use by any given scheduling
list) and the list which had the least bandwidth in use.
All lists of the appropriate frequency are checked
Arguments:
DeviceData - pointer to this controller's data area
List - must be a base scheduling list.
I.e., it must be one of ED_INTERRUPT_1ms, ED_INTERRUPT_2ms,
ED_INTERRUPT_4ms, ..., ED_INTERRUPT_32ms.
BestList - Pointer to a ULONG that recieves the list number of the
list with least bandwidth in use.
Returned Value:
The maximum bandwidth in use by any of the selected lists.
--*/
{
ULONG LastList, Index;
ULONG BestBandwidth, WorstBandwidth, Bandwidth;
WorstBandwidth = 0;
BestBandwidth = ~(ULONG) 0;
for (LastList = List + List; List <= LastList; List++) {
//
// Sum bandwidth in use in this scheduling time
//
Bandwidth = 0;
for (Index = List;
Index != ED_EOF;
Index = DeviceData->EDList[Index].Next) {
Bandwidth += DeviceData->EDList[Index].Bandwidth;
}
//
// Remember best and worst
//
if (Bandwidth < BestBandwidth) {
BestBandwidth = Bandwidth;
if (BestList != NULL) {
*BestList = List;
}
}
if (Bandwidth > WorstBandwidth) {
WorstBandwidth = Bandwidth;
}
}
LOGENTRY(G, 'ckBW', Bandwidth, 0, WorstBandwidth);
return WorstBandwidth;
}
PHCD_ENDPOINT_DESCRIPTOR
InsertEDForEndpoint(
IN PHCD_DEVICE_DATA DeviceData,
IN PHCD_ENDPOINT Endpoint,
IN UCHAR ListIndex,
IN PHCD_TRANSFER_DESCRIPTOR *TailTd
)
/*++
Routine Description:
Insert an endpoint into the h/w schedule, optionally this will
allocate an endpoint descriptor and/or a dummy transfer descriptor.
Arguments:
Endpoint - pointer to the endpoint to be included in schedule
if this parameter is NULL then we are inserting a dummy ED
that has no associted endpoint.
--*/
{
PHCD_ED_LIST list;
PHCD_ENDPOINT_DESCRIPTOR tailED, ed;
PHCD_TRANSFER_DESCRIPTOR td;
KIRQL oldIrql;
UCHAR endpointType;
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_CALL_TRACE,
("'InsertEDForEndpoint %x\n", Endpoint));
list = &DeviceData->EDList[ListIndex];
LOGENTRY(G, 'inED', Endpoint, ListIndex, list);
if (Endpoint == NULL) {
// this is a dummy ED
td = NULL;
ed = NULL;
} else {
// we have an endpoint
td = Endpoint->HcdHeadP;
ed = Endpoint->HcdED;
}
if (td == NULL) {
//
// no TD,
// get a dummy TD and attach to the endpoint
//
if (td = OpenHCI_Alloc_HcdTD(DeviceData)) {
td->UsbdRequest = TD_NOREQUEST_SIG;
// if an endpoint is specified then we want this TD to be
// the dummy TD that the haed & tail point to for a real
// endpoint
if (Endpoint != NULL) {
Endpoint->HcdHeadP = Endpoint->HcdTailP = td;
}
// return the tail td if asked
if (TailTd) {
*TailTd = td;
}
}
LOGENTRY(G, 'dyTD', Endpoint, td, list);
}
if (ed == NULL) {
//
// Need an ED,
// Get an ED, attach it to endpoint if we have one
//
ed = OpenHCI_Alloc_HcdED(DeviceData);
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_CALL_TRACE,
("'InsertEDForEndpoint ED = %x\n", ed));
if (Endpoint && ed) {
Endpoint->HcdED = ed;
}
LOGENTRY(G, 'dyED', Endpoint, ed, 0);
}
OHCI_ASSERT(td != 0);
OHCI_ASSERT(ed != 0);
if (ed == NULL || td == NULL) {
// we are probably screwed
OpenHCI_KdTrap(("'failed to alloc dummy TD/ED\n"));
return NULL;
}
//
// Initialize an endpoint descriptor for this endpoint
//
if (Endpoint != NULL) {
//
// This is for a real endpoint
//
ed->HcED.Control = Endpoint->Control;
endpointType = Endpoint->Type;
} else {
//
// no real endpoint exists, this is a dummy ED
// used to work around a hardware bug, we set the skip
// bit so the HC does not process it.
//
ed->HcED.Control = HcEDControl_SKIP;
if (ListIndex == ED_BULK) {
endpointType = USB_ENDPOINT_TYPE_BULK;
} else if (ListIndex == ED_CONTROL) {
endpointType = USB_ENDPOINT_TYPE_CONTROL;
} else if (ListIndex == ED_ISOCHRONOUS) {
endpointType = USB_ENDPOINT_TYPE_ISOCHRONOUS;
} else {
endpointType = USB_ENDPOINT_TYPE_INTERRUPT;
}
}
ed->Endpoint = Endpoint;
ed->ListIndex = ListIndex;
ed->PauseFlag = HCD_ED_PAUSE_NOT_PAUSED;
ed->HcED.TailP = ed->HcED.HeadP = td->PhysicalAddress;
KeAcquireSpinLock(&DeviceData->EDListSpin, &oldIrql);
//
// Link endpoint descriptor into HCD tracking queue
//
if (endpointType != USB_ENDPOINT_TYPE_ISOCHRONOUS ||
IsListEmpty(&list->Head)) {
//
// if it is not an iso ED or there are no EDs in the list
// then link it to the head of the hw queue
//
InsertHeadList(&list->Head, &ed->Link);
if (list->HcRegister) {
// update the hardware register that points to the list head
LOGENTRY(G, 'INH1', list, ed, list->PhysicalHead);
// tail points to old head
ed->HcED.NextED = READ_REGISTER_ULONG(list->PhysicalHead);
// new head is this ed
WRITE_REGISTER_ULONG(list->PhysicalHead, ed->PhysicalAddress);
} else {
LOGENTRY(G, 'INH2', list, ed, list->PhysicalHead);
// tail points to old head
ed->HcED.NextED = *list->PhysicalHead;
// new head is this ed
*list->PhysicalHead = ed->PhysicalAddress;
}
} else {
//
// Something already on the list,
// Link ED into tail of ED list
//
tailED = CONTAINING_RECORD(list->Head.Blink,
HCD_ENDPOINT_DESCRIPTOR,
Link);
LOGENTRY(G, 'INT1', list, ed, 0);
InsertTailList(&list->Head, &ed->Link);
ed->HcED.NextED = 0;
tailED->HcED.NextED = ed->PhysicalAddress;
}
KeReleaseSpinLock(&DeviceData->EDListSpin, oldIrql);
return (ed);
}
VOID
RemoveEDForEndpoint(
IN PHCD_ENDPOINT Endpoint
)
/*++
Routine Description:
Remove an endpoint from the hardware schedule.
Arguments:
Endpouint - the endpoint assocaited with the ED to remove.
An Endpoint pointing to the Endpoint Descriptor to be removed.
if breakEDLink then the link between the Endpoint Descriptor and its
corresponding endpoint is severed.
If this link is severed then the Endpoint Descriptor will be returned
to the descriptor free pool during the next interrupt.
--*/
{
PHCD_DEVICE_DATA DeviceData;
PHCD_ED_LIST list;
PHCD_ENDPOINT_DESCRIPTOR previousED, ed = Endpoint->HcdED;
ULONG listDisable;
KIRQL oldIrql;
UCHAR tmp;
PHC_OPERATIONAL_REGISTER HC;
ASSERT_ENDPOINT(Endpoint);
DeviceData = Endpoint->DeviceData;
list = &DeviceData->EDList[Endpoint->ListIndex];
LOGENTRY(G, 'RMed', Endpoint, ed, list);
// if we are in the active list we need to remove ourselves
KeAcquireSpinLock(&DeviceData->HcDmaSpin, &oldIrql);
if (Endpoint->EpFlags & EP_IN_ACTIVE_LIST) {
RemoveEntryList(&Endpoint->EndpointListEntry);
CLR_EPFLAG(Endpoint, EP_IN_ACTIVE_LIST);
}
KeReleaseSpinLock(&DeviceData->HcDmaSpin, oldIrql);
/* Prevent the Host Controller from processing this ED */
ed->HcED.sKip = TRUE;
KeAcquireSpinLock(&DeviceData->EDListSpin, &oldIrql);
{
/* Unlink the ED from the physical ED list */
LOGENTRY(G, 'ULed', Endpoint, ed, list);
if (&list->Head == ed->Link.Blink) {
if (list->HcRegister) {
WRITE_REGISTER_ULONG(list->PhysicalHead, ed->HcED.NextED);
} else {
*list->PhysicalHead = ed->HcED.NextED;
}
previousED = NULL;
} else {
previousED =
CONTAINING_RECORD(ed->Link.Blink,
HCD_ENDPOINT_DESCRIPTOR,
Link);
previousED->HcED.NextED = ed->HcED.NextED;
}
/* Unlink the ED from HCD list */
RemoveEntryList(&ed->Link);
ed->ListIndex = ED_EOF;
}
KeReleaseSpinLock(&DeviceData->EDListSpin, oldIrql);
/* Break the Endpoint / ED connection. This descriptor is now heading for
* the slaughter. */
Endpoint->HcdED = NULL;
ed->Endpoint = NULL;
OHCI_ASSERT(Endpoint->HcdHeadP == Endpoint->HcdTailP);
OHCI_ASSERT((ed->HcED.HeadP & ~15) == ed->HcED.TailP);
/* AKA there are no transfers on this queue. The HC will not touch any of
* these TD's. We can free the 'sentinel' TD here, but we CANNOT actually
* zero out the head and tail pointers, because the HC could still be
* looking at this descriptor. Later, in the irq, when we take this
* endpoint off the reclamation list, we much free the ED, and ignore the
* non zero values of HeadP and TailP. */
OpenHCI_Free_HcdTD(DeviceData, Endpoint->HcdHeadP);
/*
* The control and bulk lists are round robbin. Therefore we need to
* disable these lists to insure that the 'current ED' pointer is not
* pointing to that which we are removing.
*/
switch (Endpoint->ListIndex) {
case ED_CONTROL:
listDisable = ~(ULONG) HcCtrl_ControlListEnable;
break;
case ED_BULK:
listDisable = ~(ULONG) HcCtrl_BulkListEnable;
break;
default:
list->Bandwidth -= Endpoint->Bandwidth;
DeviceData->MaxBandwidthInUse
= OpenHCI_CheckBandwidth(DeviceData,
ED_INTERRUPT_32ms,
&tmp);
listDisable = 0;
}
HC = DeviceData->HC;
WRITE_REGISTER_ULONG(&HC->HcInterruptStatus, HcInt_StartOfFrame);
/* Clear the SOF interrupt pending status */
{
if (listDisable) {
KeSynch_HcControl context;
context.NewHcControl.ul = listDisable;
context.DeviceData = DeviceData;
KeSynchronizeExecution(DeviceData->InterruptObject,
OpenHCI_HcControl_AND,
&context);
KeAcquireSpinLock(&DeviceData->ReclamationSpin, &oldIrql);
InsertTailList(&DeviceData->StalledEDReclamation, &ed->Link);
} else {
KeAcquireSpinLock(&DeviceData->ReclamationSpin, &oldIrql);
InsertTailList(&DeviceData->RunningEDReclamation, &ed->Link);
}
ed->ReclamationFrame = Get32BitFrameNumber(DeviceData) + 1;
WRITE_REGISTER_ULONG(&HC->HcInterruptEnable, HcInt_StartOfFrame);
}
KeReleaseSpinLock(&DeviceData->ReclamationSpin, oldIrql);
// free our descriptors and endpoint
OpenHCI_UnReserveDescriptors(DeviceData, Endpoint->DescriptorsReserved);
// now free the endpoint
Endpoint->Sig = 0;
ExFreePool(Endpoint);
}
NTSTATUS
OpenHCI_OpenEndpoint(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PHCD_DEVICE_DATA DeviceData,
IN OUT PHCD_URB urb
)
/*++
Routine Description:
Create a OpenHCI endpoint, this function is called from
OpenHCI_URB_Dispatch to create a new endpoint structure.
There are three things that can cause this routine to fail:
1) ExAllocatePool may fail to allocate an HCD_ENDPOINT,
2) OpenHCI_ReserveDescriptors may not be able to find enough
descriptors, and
3) OpenHCI_ReserveBandwidth may not be able to find bandwidth
in the schedule.
This routine is simplified because USBD serializes OpenEndpoint
and CloseEndpoint URBs
Arguments:
DeviceObject - pointer to a device object
Irp - pointer to an I/O Request Packet
Return Value:
NT Status code
--*/
{
PHCD_ENDPOINT endpoint;
PUSB_ENDPOINT_DESCRIPTOR endpointDescriptor;
UCHAR WhichList;
NTSTATUS ntStatus = STATUS_SUCCESS;
ULONG tdsNeeded;
OpenHCI_KdPrintDD(DeviceData,
OHCI_DBG_END_TRACE, ("'enter OpenEndpoint\n"));
#if DBG
/*
* We are assuming that the caller of _OpenEndpoint and _Close endpoint
* make only serial calls. For this reason we will protect ourselves. If
* this is violated we return STATUS_DEVICE_BUSY.
*/
if (0 < InterlockedIncrement(&DeviceData->OpenCloseSync)) {
OpenHCI_KdTrap(("'ohciurb.c: _OpenEndpoint: Non serial call! %d",
DeviceData->OpenCloseSync));
TEST_TRAP();
return STATUS_DEVICE_BUSY;
}
#endif // DBG
//
// make sure the length of the urb is what we expect
//
if (urb->HcdUrbOpenEndpoint.Length !=
sizeof(struct _URB_HCD_OPEN_ENDPOINT)) {
urb->UrbHeader.Status = USBD_STATUS_INVALID_PARAMETER;
return STATUS_INVALID_PARAMETER;
}
urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
//
// information about the endpoint comes from the USB endpoint
// descriptor passed in the URB.
//
endpointDescriptor = urb->HcdUrbOpenEndpoint.EndpointDescriptor;
urb->HcdUrbOpenEndpoint.HcdEndpoint = NULL;
// How does one transmit to an endpoint that does not have
// a positive max packet size?
// A: this is an error
if (endpointDescriptor->wMaxPacketSize == 0) {
endpoint =
ZERO_LOAD_ENDPOINT(DeviceData);
urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
ntStatus = STATUS_SUCCESS;
goto OpenHCI_OpenEndpoint_Done;
}
//
// Allocate resources for the endpoint, this includes an endpoint
// handle that will be passed to us in subsequent transfer requests
//
endpoint = (PHCD_ENDPOINT)
ExAllocatePoolWithTag(NonPagedPool,
sizeof(HCD_ENDPOINT),
OpenHCI_TAG);
if (endpoint == NULL) {
urb->UrbHeader.Status = USBD_STATUS_NO_MEMORY;
ntStatus = STATUS_INSUFFICIENT_RESOURCES;
goto OpenHCI_OpenEndpoint_Done;
}
//
// Start initializing the endpoint structure
//
InitializeListHead(&endpoint->RequestQueue);
endpoint->Sig = SIG_EP;
endpoint->EpFlags = 0;
endpoint->LowSpeed = FALSE;
endpoint->Isochronous = FALSE;
endpoint->Rate = endpointDescriptor->bInterval;
// Initial conditon:
// No active transfers
// Max of two active transfers
//
endpoint->EndpointStatus = 0;
endpoint->MaxRequest = 2;
endpoint->AbortIrp = NULL;
endpoint->Type =
endpointDescriptor->bmAttributes & USB_ENDPOINT_TYPE_MASK;
if (DeviceData->HcFlags & HC_FLAG_SLOW_BULK_ENABLE &&
endpoint->Type == USB_ENDPOINT_TYPE_BULK) {
endpoint->MaxRequest = 1; // limit to only one transfer
SET_EPFLAG(endpoint, EP_ONE_TD);
}
endpoint->ListIndex = ED_EOF; // not on a list yet
endpoint->Bandwidth = 0; // assume bulk or control
endpoint->DeviceData = DeviceData;
endpoint->Control = 0;
endpoint->HcdED = NULL;
endpoint->HcdHeadP = endpoint->HcdTailP = NULL;
endpoint->DescriptorsReserved = 0;
endpoint->TrueTail = NULL;
KeInitializeSpinLock(&endpoint->QueueLock);
SET_EPFLAG(endpoint, EP_VIRGIN);
endpoint->FunctionAddress = urb->HcdUrbOpenEndpoint.DeviceAddress;
endpoint->EndpointNumber = endpointDescriptor->bEndpointAddress;
if (endpoint->Type == USB_ENDPOINT_TYPE_CONTROL) {
endpoint->Direction = HcEDDirection_Defer;
} else if (endpointDescriptor->bEndpointAddress & 0x80) {
endpoint->Direction = HcEDDirection_In;
} else {
endpoint->Direction = HcEDDirection_Out;
}
if (urb->HcdUrbOpenEndpoint.HcdEndpointFlags & USBD_EP_FLAG_LOWSPEED) {
endpoint->LowSpeed = TRUE;
}
if (endpoint->Type == USB_ENDPOINT_TYPE_ISOCHRONOUS) {
endpoint->Isochronous = TRUE;
}
endpoint->MaxPacket = endpointDescriptor->wMaxPacketSize;
//
// Figure out how many descriptors we need to reserve
//
endpoint->MaxTransfer = urb->HcdUrbOpenEndpoint.MaxTransferSize;
if (endpoint->FunctionAddress != DeviceData->RootHubAddress) {
//
// This is an ordinary device endpoint...
//
// the goal here is to reserve enough TDs so that we can
// program the largest transfer to the hardware
//
if (endpoint->Type == USB_ENDPOINT_TYPE_ISOCHRONOUS)
{
ULONG packetSize;
ULONG maxTransfer;
ULONG msPerTransfer;
ULONG packetsPerTD;
maxTransfer = endpoint->MaxTransfer;
packetSize = endpoint->MaxPacket;
// Assume that the client driver sized the MaxTransferSize by
// multiplying the MaxPacketSize by the maximum number of frames
// the client driver would submit in a single request, so compute
// the maximun number of frames per request by dividing the
// MaxTransferSize by the MaxPacketSize.
//
msPerTransfer = maxTransfer / packetSize;
// Isoch transfers are limited to 255 packets per transfer.
//
if (msPerTransfer > 255)
{
msPerTransfer = 255;
}
// A TD can only span one page crossing. In the worst cast we
// can only fit a page worth of packets into a single TD to
// guarantee that there is only one page crossing.
//
// packetSize packetsPerTD (worst case)
// 1 - 512 8
// 513 - 585 7
// 586 - 682 6
// 683 - 819 5
// 820 - 1023 4
//
packetsPerTD = OHCI_PAGE_SIZE / packetSize;
// Regardless of the MaxPacketSize, a single TD is limited to
// eight packets.
//
if (packetsPerTD > 8)
{
packetsPerTD = 8;
}
// Calculate the number of TDs needed by dividing the maximum
// number of frames per transfer (rounded up) by the worst case
// miminum number of frames that will fit in a TD
//
tdsNeeded = (msPerTransfer + packetsPerTD - 1) / packetsPerTD;
LOGENTRY(G, 'rISO', tdsNeeded, packetSize, maxTransfer);
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_END_TRACE,
("'reserve desc for iso ep (%d) pct siz = %d, max xfer = %d\n",
tdsNeeded, packetSize, maxTransfer));
// If the magic endpoint flag is set bump the MaxRequests
// all the way up to 32.
//
if (urb->HcdUrbOpenEndpoint.HcdEndpointFlags &
USBD_EP_FLAG_MAP_ADD_IO)
{
endpoint->MaxRequest = 32;
}
} else {
// we will need a TD for each page of data to transfer,
// worst case
tdsNeeded = (endpoint->MaxTransfer / (OHCI_PAGE_SIZE+1)) + 1;
// we need an xtra TD for the setup/status packet
if (endpoint->Type == USB_ENDPOINT_TYPE_CONTROL) {
tdsNeeded+=2;
}
}
// We need enough TDs to queue MaxRequest transfers plus:
// two EDs (why two EDs instead of one?), a dummy TD for the
// tail TD, and a spare (why?)
//
tdsNeeded = (tdsNeeded * endpoint->MaxRequest) + 4;
//
// pre-allocate the number of descriptors we will need
// for this endpoint
//
ntStatus = OpenHCI_ReserveDescriptors(DeviceData, tdsNeeded);
LOGENTRY(G, 'rTDS', endpoint, tdsNeeded, 0);
if (!NT_SUCCESS(ntStatus)) {
TEST_TRAP();
urb->UrbHeader.Status = USBD_STATUS_NO_MEMORY;
goto OpenHCI_OpenEndpoint_Done;
}
endpoint->DescriptorsReserved = tdsNeeded;
endpoint->Bandwidth =
(USHORT) USBD_CalculateUsbBandwidth(endpoint->MaxPacket,
endpoint->Type,
(BOOLEAN)endpoint->LowSpeed);
LOGENTRY(G, 'ndBW', endpoint, endpoint->Bandwidth, 0);
switch(endpoint->Type) {
case USB_ENDPOINT_TYPE_ISOCHRONOUS:
WhichList = ED_ISOCHRONOUS;
DeviceData->EDList[ED_ISOCHRONOUS].Bandwidth +=
endpoint->Bandwidth;
DeviceData->MaxBandwidthInUse += endpoint->Bandwidth;
break;
case USB_ENDPOINT_TYPE_INTERRUPT:
//
// Determine the scheduling period.
//
WhichList = ED_INTERRUPT_32ms;
while (WhichList >= endpoint->Rate && (WhichList >>= 1)) {
//
// Keep decrementing the schedule list until, the list
// chosen
// meets or exceeds the rate required by the endpoint.
//
continue;
}
//
// Determine which scheduling list has the least bandwidth
//
OpenHCI_CheckBandwidth(DeviceData, WhichList, &WhichList);
DeviceData->EDList[WhichList].Bandwidth += endpoint->Bandwidth;
//
// Recalculate the max bandwidth which is in use. This
// allows 1ms (isoc) pipe opens to occur with few calculation
//
DeviceData->MaxBandwidthInUse =
OpenHCI_CheckBandwidth(DeviceData,
ED_INTERRUPT_32ms,
NULL);
break;
case USB_ENDPOINT_TYPE_BULK:
WhichList = ED_BULK;
break;
case USB_ENDPOINT_TYPE_CONTROL:
WhichList = ED_CONTROL;
break;
}
endpoint->ListIndex = WhichList;
urb->HcdUrbOpenEndpoint.ScheduleOffset = WhichList;
//
// Verify the new max has not overcomitted the USB
//
LOGENTRY(G, 'vrBW', endpoint, DeviceData->MaxBandwidthInUse,
DeviceData->AvailableBandwidth);
if (DeviceData->MaxBandwidthInUse > DeviceData->AvailableBandwidth) {
//
// Too much, back this bandwidth out and fail the request
//
DeviceData->EDList[WhichList].Bandwidth -= endpoint->Bandwidth;
DeviceData->MaxBandwidthInUse =
OpenHCI_CheckBandwidth(DeviceData,
ED_INTERRUPT_32ms,
NULL);
//
// return a CAN_NOT_COMMIT_BANDWIDTH error.
urb->UrbHeader.Status = USBD_STATUS_NO_BANDWIDTH;
ntStatus = STATUS_UNSUCCESSFUL;
goto OpenHCI_OpenEndpoint_Done;
}
//
// Add to Host Controller schedule processing
//
// if lowspeed control and Hs/ls hack enabled
// put control transfers on the periodic list
if (endpoint->LowSpeed &&
DeviceData->HcFlags & HC_FLAG_USE_HYDRA_HACK &&
endpoint->ListIndex == ED_CONTROL) {
OpenHCI_KdPrint((1, "'*** do hs/ls fix for control ep\n"));
// put control on the interrupt list
endpoint->ListIndex = ED_INTERRUPT_1ms;
}
InsertEDForEndpoint(endpoint->DeviceData,
endpoint,
endpoint->ListIndex,
NULL);
OpenHCI_KdPrintDD(DeviceData,
OHCI_DBG_END_TRACE, ("'Open Endpoint:\n"));
} else {
//
// note that if the controller has lost power
// before the RH is started we need to restore the HC state
// before initializing the root hub.
//
if (DeviceData->HcFlags & HC_FLAG_LOST_POWER) {
BOOLEAN lostPower;
PHC_OPERATIONAL_REGISTER HC;
HC = DeviceData->HC;
// should only get here in the case where the
// HC looses power
//
OpenHCI_RestoreHCstate(DeviceData, &lostPower);
OHCI_ASSERT(lostPower == TRUE);
OpenHCI_Resume(DeviceObject, lostPower);
KeSetTimerEx(&DeviceData->DeadmanTimer,
RtlConvertLongToLargeInteger(-10000),
10,
&DeviceData->DeadmanTimerDPC);
}
//
// This is an emulated Root Hub endpoint
//
SET_EPFLAG(endpoint, EP_ROOT_HUB);
if (endpoint->EndpointNumber == 1 &&
endpoint->Type == USB_ENDPOINT_TYPE_INTERRUPT) {
DeviceData->RootHubInterrupt = endpoint;
/*
* Note: if you open up two endpoints to the Interrupt endpoint
* of the root hub then the first one stops responding. But this
* is only fare as you should not open up two pipes to the same
* device and endpoint number.
*/
} else {
DeviceData->RootHubControl = endpoint;
}
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_END_INFO,
("'Open Endp (Root Hub Emulation)\n"));
}
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_END_INFO,
("'ED: Type %d, Dir %d Addr %d FUNC Address %d LowSpeed %d\n",
endpoint->Type,
endpoint->Direction,
endpoint->EndpointNumber,
endpoint->FunctionAddress,
endpoint->LowSpeed));
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_END_INFO,
("' MaxPacket %x Interval Requested %d List Selected %d\n",
endpoint->MaxPacket,
endpointDescriptor->bInterval,
endpoint->ListIndex));
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_END_INFO,
("'*****HCD_ENDPOINT addr: 0x%08x HcdED: 0x%08x\n\n\n",
endpoint, endpoint->HcdED));
OpenHCI_OpenEndpoint_Done:
//
// Complete the IRP, status is in the status field of the URB
//
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_END_TRACE,
("'exit OpenHCI_OpenEndpoint (URB STATUS = %x)\n",
urb->UrbHeader.Status));
if (NT_SUCCESS(ntStatus)) {
urb->HcdUrbOpenEndpoint.HcdEndpoint = endpoint;
} else {
ASSERT(endpoint != ZERO_LOAD_ENDPOINT(DeviceData));
if (endpoint) {
OpenHCI_UnReserveDescriptors(DeviceData,
endpoint->DescriptorsReserved);
OHCI_ASSERT(!(endpoint->EpFlags & EP_IN_ACTIVE_LIST));
ExFreePool(endpoint);
}
}
#if DBG
InterlockedDecrement(&DeviceData->OpenCloseSync);
#endif //DBG
LOGENTRY(G, 'opEP', endpoint, urb, ntStatus);
return ntStatus;
}
NTSTATUS
OpenHCI_CloseEndpoint(
IN PDEVICE_OBJECT DeviceObject,
IN PIRP Irp,
IN PHCD_DEVICE_DATA DeviceData,
IN OUT PHCD_URB urb
)
/*++
Routine Description:
Free a OpenHCI endpoint, if there are any pending transfers for this
endpoint this routine should fail.
Arguments:
DeviceObject - pointer to a device object
Irp - pointer to an I/O Request Packet
Return Value:
If out of synchronous call with close and open
then return STATUS_DEVICE_BUSY otherwise
return STATUS_SUCCESS
--*/
{
PHCD_ENDPOINT endpoint = urb->HcdUrbCloseEndpoint.HcdEndpoint;
PHCD_ENDPOINT_DESCRIPTOR ed;
BOOLEAN outstandingTransfers, activeTransfers;
KIRQL irql;
NTSTATUS ntStatus = STATUS_SUCCESS;
BOOLEAN freeEP = TRUE;
// LARGE_INTEGER time;
OpenHCI_KdPrintDD(DeviceData,
OHCI_DBG_END_TRACE, ("'enter CloseEndpoint\n"));
LOGENTRY(G, 'rcEP', endpoint, urb, 0);
#if DBG
/* We are assuming that the caller of _OpenEndpoint and _Close endpoint
* make only serial calls. For this reason we will protect ourselves. If
* this is violated we return STATUS_DEVICE_BUSY. */
if (0 < InterlockedIncrement(&DeviceData->OpenCloseSync)) {
OpenHCI_KdTrap(("'ohciurb.c: _OpenEndpoint: Non serial call! %d",
DeviceData->OpenCloseSync));
urb->UrbHeader.Status = USBD_STATUS_INTERNAL_HC_ERROR;
ntStatus = STATUS_UNSUCCESSFUL;
goto CloseEndpoint_Done;
}
#endif // DBG
if (endpoint == ZERO_LOAD_ENDPOINT(DeviceData)) {
urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
ntStatus = STATUS_SUCCESS;
goto CloseEndpoint_Done;
}
ASSERT_ENDPOINT(endpoint);
// mark the endpoint as closed -- this prevents any more transers from
// being queued, if we fail the close we can clear the close flag.
// NOTE: if someone is sending transfers while we are closing they have a
// problem anyway this will just let us finish the close.
SET_EPFLAG(endpoint, EP_CLOSED);
OpenHCI_LockAndCheckEndpoint(endpoint,
&outstandingTransfers,
&activeTransfers,
&irql);
OpenHCI_UnlockEndpoint(endpoint, irql);
if (outstandingTransfers ||
activeTransfers) {
//
// fail if we have pending transfers,
// note: USBD should prevent this
//
// if we get here we probably have a problem somewhere
TRAP();
urb->UrbHeader.Status = USBD_STATUS_INTERNAL_HC_ERROR;
OpenHCI_KdPrintDD(DeviceData,
OHCI_DBG_END_ERROR,
("'exit OutstandingTRANS OpenHCI_CloseEndpoint\n"));
ntStatus = STATUS_UNSUCCESSFUL;
goto CloseEndpoint_Done;
}
ed = endpoint->HcdED;
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_END_INFO,
("'Closing Endpoint: %x\n", endpoint));
if (ed) {
KeAcquireSpinLock(&DeviceData->PausedSpin, &irql);
if (ed->PauseFlag != HCD_ED_PAUSE_NOT_PAUSED)
{
// This endpoint is paused.
//
// we are waiting for the CancelTDsForED to complete, we set a
// flag here that tells CancelTDsForED to call the remove ED
// function.
//
LOGENTRY(G, 'cle1', endpoint, ed, 0);
SET_EPFLAG(endpoint, EP_FREE);
KeReleaseSpinLock(&DeviceData->PausedSpin, irql);
OpenHCI_KdPrintDD(DeviceData,
OHCI_DBG_END_ERROR, ("'closing paused \n"));
freeEP = FALSE;
} else {
LOGENTRY(G, 'cle2', endpoint, ed, 0);
KeReleaseSpinLock(&DeviceData->PausedSpin, irql);
if (ED_EOF != endpoint->ListIndex) {
RemoveEDForEndpoint(endpoint);
freeEP = FALSE;
}
}
}
//
// we return our BW in RemoveEDForEndpoint
//
if (freeEP) {
ASSERT_ENDPOINT(endpoint);
// free our descriptors and endpoint
OpenHCI_UnReserveDescriptors(DeviceData, endpoint->DescriptorsReserved);
if (DeviceData->RootHubInterrupt == endpoint) {
DeviceData->RootHubInterrupt = NULL;
OpenHCI_KdPrintDD(DeviceData,
OHCI_DBG_END_TRACE, ("'closed RH interrupt\n"));
}
if (DeviceData->RootHubControl == endpoint) {
DeviceData->RootHubControl = NULL;
// root hub is no longer configured
DeviceData->RootHubConfig = 0;
OpenHCI_KdPrintDD(DeviceData,
OHCI_DBG_END_TRACE, ("'closed RH control\n"));
if (endpoint->FunctionAddress != 0) {
DeviceData->RootHubAddress = 0;
}
}
endpoint->Sig = 0;
OHCI_ASSERT(!(endpoint->EpFlags & EP_IN_ACTIVE_LIST));
ExFreePool(endpoint);
}
urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
CloseEndpoint_Done:
#if DBG
InterlockedDecrement(&DeviceData->OpenCloseSync);
if (!NT_SUCCESS(ntStatus)) {
// check why we are failing the close
TEST_TRAP();
}
#endif
LOGENTRY(G, 'clEP', 0, urb, ntStatus);
return ntStatus;
}
ULONG
Get32BitFrameNumber(
PHCD_DEVICE_DATA DeviceData
)
{
ULONG hp, fn, n;
/* This code accounts for the fact that HccaFrameNumber is updated by the
* HC before the HCD gets an interrupt that will adjust FrameHighPart. No
* synchronization is nescisary due to great cleaverness. */
hp = DeviceData->FrameHighPart;
fn = DeviceData->HCCA->HccaFrameNumber;
n = ((fn & 0x7FFF) | hp) + ((fn ^ hp) & 0x8000);
// Note: we can't log here because this function is called from the ISR
return n;
}
VOID
OpenHCI_PauseED(
IN PHCD_ENDPOINT Endpoint
)
/*++
Routine Description:
Processing of an endpoint by the HC must be paused before any
maintenance is performend on the TD list. This function first
sets the skip bit and then places the ED on a separate paused
list. At the next Start of Frame this ED can then be worked on.
Presumably the irq function will call CancelTDsForED on this
endpoint at that time.
Arguments:
Endpoint - then endpoint that need pausing.
--*/
{
PHCD_DEVICE_DATA DeviceData;
PHCD_ENDPOINT_DESCRIPTOR ed;
KIRQL oldirq;
PHC_OPERATIONAL_REGISTER HC;
ASSERT_ENDPOINT(Endpoint);
DeviceData = Endpoint->DeviceData;
HC = DeviceData->HC;
ed = Endpoint->HcdED;
OpenHCI_KdPrintDD(DeviceData,
OHCI_DBG_TD_NOISE, ("'Pausing Endpoint\n"));
LOGENTRY(G, 'Rpau', DeviceData, Endpoint, ed);
KeAcquireSpinLock(&DeviceData->PausedSpin, &oldirq);
// Are we already pasued?
//
if (ed->PauseFlag == HCD_ED_PAUSE_NOT_PAUSED)
{
// No, pause this endpoint
ed->HcED.sKip = TRUE;
// Clear any currently pending SOF interrupt
//
WRITE_REGISTER_ULONG(&HC->HcInterruptStatus, HcInt_StartOfFrame);
// It will be safe to operate on the endpoint in the next frame
//
ed->ReclamationFrame = Get32BitFrameNumber(DeviceData) + 1;
// Put this endpoint on the list of endpoints that OpenHCI_IsrDPC()
// should pass to OpenHCI_CancelTDsForED()
//
InsertTailList(&DeviceData->PausedEDRestart, &ed->PausedLink);
// Make sure SOF interrupts are enabled
//
WRITE_REGISTER_ULONG(&HC->HcInterruptEnable, HcInt_StartOfFrame);
LOGENTRY(G, 'paus', DeviceData, Endpoint, ed);
}
#if DBG
else {
OpenHCI_KdPrintDD(DeviceData, OHCI_DBG_TD_ERROR,
("'Warning Endpoint already paused!\n"));
}
#endif
// Indicate that OpenHCI_CancelTDsForED() needs to make a pass through
// the requests queued on this endpoint.
//
ed->PauseFlag = HCD_ED_PAUSE_NEEDED;
KeReleaseSpinLock(&DeviceData->PausedSpin, oldirq);
return;
}
BOOLEAN
OpenHCI_HcControl_OR(
IN OUT PVOID Context
)
/*++
Routine Description:
This function is used in connection with KeSynchronizeExecution
to set the bits of the HcControl register which is accessed by
all function including the interrupt routine (not just the IsrDPC).
This function reads the CurrentHcControl field of the device
extension and ORs in the bits given by NewHcControl. It then
writes the results to the register.
Reads of the register take a long time so the value is cached.
Results:
This function modifies the input pointer to HC_CONTROL field
(PNewHcControl) to the result of the OR operation.
--*/
{
PHC_CONTROL newControl = &((PKeSynch_HcControl) Context)->NewHcControl;
PHCD_DEVICE_DATA DeviceData = ((PKeSynch_HcControl) Context)->DeviceData;
DeviceData->CurrentHcControl.ul
= READ_REGISTER_ULONG(&DeviceData->HC->HcControl.ul);
newControl->ul = (DeviceData->CurrentHcControl.ul |= newControl->ul);
WRITE_REGISTER_ULONG(&DeviceData->HC->HcControl.ul, newControl->ul);
return TRUE;
}
BOOLEAN
OpenHCI_HcControl_AND(
PVOID Context
)
/**
Routine Description:
OpenHCI_HcControl_OR with a twist.
--*/
{
PHC_CONTROL newControl = &((PKeSynch_HcControl) Context)->NewHcControl;
PHCD_DEVICE_DATA DeviceData = ((PKeSynch_HcControl) Context)->DeviceData;
DeviceData->CurrentHcControl.ul
= READ_REGISTER_ULONG(&DeviceData->HC->HcControl.ul);
newControl->ul = (DeviceData->CurrentHcControl.ul &= newControl->ul);
WRITE_REGISTER_ULONG(&DeviceData->HC->HcControl.ul, newControl->ul);
return TRUE;
}
BOOLEAN
OpenHCI_HcControl_SetHCFS(
PVOID Context
)
/**
Routine Description:
Mask out the Functional State in the HcControl register
and in its place put the ulong.
--*/
{
PHC_CONTROL newControl = &((PKeSynch_HcControl) Context)->NewHcControl;
PHCD_DEVICE_DATA DeviceData = ((PKeSynch_HcControl) Context)->DeviceData;
DeviceData->CurrentHcControl.ul
= READ_REGISTER_ULONG(&DeviceData->HC->HcControl.ul);
newControl->ul &= HcCtrl_HCFS_MASK;
DeviceData->CurrentHcControl.ul &= ~HcCtrl_HCFS_MASK;
newControl->ul = (DeviceData->CurrentHcControl.ul |= newControl->ul);
WRITE_REGISTER_ULONG(&DeviceData->HC->HcControl.ul, newControl->ul);
return TRUE;
}
BOOLEAN
OpenHCI_ListEnablesAtNextSOF(
PVOID Context
)
/**
Routine Description:
Set the ListEnablesAtNextSOF value in DeviceData so that when the
interrupt comes those lists will be enabled.
--*/
{
((PKeSynch_HcControl) Context)->DeviceData->ListEnablesAtNextSOF.ul
= HcCtrl_ListEnableMask & ((PKeSynch_HcControl) Context)->
NewHcControl.ul;
return TRUE;
}