/*++

Copyright (c) 1995,1996 Microsoft Corporation
:ts=4

Module Name:

    urb.c

Abstract:

    The module manages transactions on the USB.

Environment:

    kernel mode only

Notes:

Revision History:

    11-01-95 : created
    04-28-96 : linked urb support & cancel support

--*/
#include "wdm.h"
#include "stdarg.h"
#include "stdio.h"


#include "usbdi.h"
#include "hcdi.h"
#include "uhcd.h"


#define COMPUTE_HCD_EXTENSION_SIZE(urb)  (sizeof(HCD_EXTENSION) + \
                    (urb->HcdUrbCommonTransfer.TransferBufferLength / PAGE_SIZE + 1) \
                    * sizeof(UHCD_LOGICAL_ADDRESS))

#define UHCD_IS_TRANSFER(urb)    (((urb)->UrbHeader.Function == URB_FUNCTION_CONTROL_TRANSFER || \
                                   (urb)->UrbHeader.Function == URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER ||\
                                   (urb)->UrbHeader.Function == URB_FUNCTION_ISOCH_TRANSFER) \
                                         ? TRUE : FALSE)



// calc the maxrequests based on endpoint type nad
// flag options
UCHAR
MAX_REQUESTS(
    IN PUSB_ENDPOINT_DESCRIPTOR EndpointDescriptor,
    IN ULONG EpFlags
    )
{

    if (EpFlags & EPFLAG_DBL_BUFFER) {
        return 1;
    }

    switch (USB_ENDPOINT_TYPE_MASK &
        EndpointDescriptor->bmAttributes) {
    case USB_ENDPOINT_TYPE_BULK:
        return 2;
        //return 1;
    case USB_ENDPOINT_TYPE_ISOCHRONOUS:
        return 3;
    default:
        return 1;
    }
}

#if 0
ULONG
UHCD_CountPageCrossings(
    IN ULONG MaxRequests,
    IN ULONG MaxTransferSize
    )
/*++

Routine Description:

    Completes an I/O Request

Arguments:


Return Value:

    maximum number of possible page crossings

--*/
{
    ULONG pageCrossings;

    pageCrossings = (MaxTransferSize + PAGE_SIZE) / PAGE_SIZE;

    if (MaxRequests>1) {
        pageCrossings *= 2;
    }

    // now allocate space for packet buffers
    UHCD_KdPrint((2, "'UHCD_CountPageCrossings, max transfer size  0x%x -- page crossings = 0x%x\n",
        MaxTransferSize, pageCrossings));

    UHCD_ASSERT(pageCrossings > 0);

    return pageCrossings;
}
#endif


VOID
UHCD_CompleteIrp(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN NTSTATUS ntStatus,
    IN ULONG Information,
    IN PHCD_URB Urb
    )
/*++

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

    Information -

    Urb - root transfer urb if this Irp is associated with
            a transfer.

Return Value:


--*/
{
    KIRQL irql;
    UCHAR flags = 0;

    STARTPROC("cIrp");

    UHCD_KdPrint((2, "'UHCD_CompleteIrp status = %x\n", ntStatus));
    LOGENTRY(LOG_MISC, 'ICan', Urb, 0, Irp);

    if (Urb) {

        //
        // if we have any working space free it now
        //

        if (UHCD_IS_TRANSFER(Urb) && HCD_AREA(Urb).HcdExtension) {
            PHCD_EXTENSION urbWork;
            urbWork = HCD_AREA(Urb).HcdExtension;
            // remember the flags
            flags = urbWork->Flags;

            RETHEAP(HCD_AREA(Urb).HcdExtension);
            HCD_AREA(Urb).HcdExtension = NULL;
        }

        //
        // Decrement the urb counter that we keep in our stack location for
        // the irp, when it goes to zero -- complete it
        //

        //
        // one Urb completed
        //

        DECREMENT_PENDING_URB_COUNT(Irp);

        if (PENDING_URB_COUNT(Irp)) {

            //
            // stall completion until all Urbs are done.
            //

            TEST_TRAP();
            return;

        } else {

            IoAcquireCancelSpinLock(&irql);

            if (Irp->Cancel) {

                LOGENTRY(LOG_MISC, 'irpX', flags, 0, Irp);

                // note that the cancel routine will only mess
                // with the urbs on the active list -- any active
                // urbs should have been removed from the active
                // list before calling this routine

                //
                // Irp associated with this transfer has
                // been canceled.
                //
                // The cancel routine will complete the Irp
                // unless there are active transfers.
                //

                if (flags & UHCD_TRANSFER_ACTIVE) {

                    IoSetCancelRoutine(Irp, NULL);

                    IoReleaseCancelSpinLock(irql);

                    //
                    // if the io has already started the we must
                    // complete the Irp with STATUS_CANCELLED here.
                    //

                    ntStatus = STATUS_CANCELLED;
                    Information = 0;

                    goto UHCD_CompleteIrp_CompleteRequest;
                }

                IoReleaseCancelSpinLock(irql);

                return;

            } else {

                //
                // Irp is no longer cancelable
                //

                LOGENTRY(LOG_MISC, 'NCan', flags, 0, Irp);

                IoSetCancelRoutine(Irp, NULL);

                IoReleaseCancelSpinLock(irql);

                //
                // Pending bit should be cleared
                //

                UHCD_ASSERT(!USBD_PENDING(Urb->HcdUrbCommonTransfer.Status));

            }

        }
    }

UHCD_CompleteIrp_CompleteRequest:

    Irp->IoStatus.Status      = ntStatus;
    Irp->IoStatus.Information = Information;

    LOGENTRY(LOG_MISC, 'irpC', Irp, DeviceObject, Urb);

    USBD_CompleteRequest(Irp,
                         IO_NO_INCREMENT);

    ENDPROC("cIrp");
}


NTSTATUS
UHCD_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;
    PDEVICE_EXTENSION deviceExtension;
    PUHCD_ENDPOINT endpoint = NULL;
    ULONG siz, cnt;
    ULONG numTDs;

    UHCD_KdPrint((2, "'enter UHCD_URB_Dispatch \n"));

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    urb = (PHCD_URB) URB_FROM_IRP(Irp);

    if (deviceExtension->CurrentDevicePowerState != PowerDeviceD0 ||
        deviceExtension->HcFlags & HCFLAG_HCD_SHUTDOWN) {
        //
        // someone is submitting requests while the
        // HC is suspended or OFF, we will just fail them
        //
        UHCD_KdPrint
            ((0, "'Warning: UHCD got a request while not in D0 in shutdown\n"));
        ntStatus = STATUS_DEVICE_NOT_READY;
        URB_HEADER(urb).Status = USBD_STATUS_REQUEST_FAILED;
        UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
        return ntStatus;
    }

    switch(URB_HEADER(urb).Function) {

    //
    // These commands get queued to the startio routine
    // for processing.
    //

    case URB_FUNCTION_HCD_OPEN_ENDPOINT:

#ifdef ROOT_HUB
        if (urb->HcdUrbOpenEndpoint.DeviceAddress ==
            deviceExtension->RootHubDeviceAddress) {

            // This routine will either complete
            // the irp or mark it pending.
            ntStatus = UHCD_RootHub_OpenEndpoint(DeviceObject,
                                                 Irp,
                                                 urb);
            break;
        }
#endif
        //
        // Grow the common buffer pool based on how much
        // memory we'll need for transfers
        //
        // Well need enough for one set of TDs plus
        // a common buffer for each possible page crossing
        // within a trasnsfer.

        // first allocate space for the TDs
        // for now use a global value

        //
        // allocate the endpoint structures here while we are
        // at passive level
        //

        endpoint = (PUHCD_ENDPOINT) GETHEAP(NonPagedPool,
                                            sizeof(UHCD_ENDPOINT));


        if (endpoint) {

            // start endpoint initialization
            RtlZeroMemory(endpoint, sizeof(*endpoint));
            endpoint->Sig = SIG_EP;

             // check for bulk and iso special options
             // we only support double buffering for bulk-IN
             // we only support fast iso for iso-OUT

            if (urb->HcdUrbOpenEndpoint.HcdEndpointFlags &
                    USBD_EP_FLAG_DOUBLE_BUFFER) {

                if (USB_ENDPOINT_DIRECTION_IN(
                        urb->HcdUrbOpenEndpoint.EndpointDescriptor->bEndpointAddress)
                    &&
                    (USB_ENDPOINT_TYPE_MASK &
                     urb->HcdUrbOpenEndpoint.EndpointDescriptor->bmAttributes)
                        ==
                     USB_ENDPOINT_TYPE_BULK) {

                    SET_EPFLAG(endpoint, EPFLAG_DBL_BUFFER);

                } else { // bugbug error here?
                    UHCD_KdPrint((1, "'WARNING: Cannot double buffer this endpoint\n"));

                }
            }

#ifdef FAST_ISO
            if (urb->HcdUrbOpenEndpoint.HcdEndpointFlags &
                    USBD_EP_FLAG_FAST_ISO) {
                if (USB_ENDPOINT_DIRECTION_OUT(
                        urb->HcdUrbOpenEndpoint.EndpointDescriptor->bEndpointAddress)
                    &&
                    (USB_ENDPOINT_TYPE_MASK &
                      urb->HcdUrbOpenEndpoint.EndpointDescriptor->bmAttributes)
                        ==
                      USB_ENDPOINT_TYPE_ISOCHRONOUS) {

                    SET_EPFLAG(endpoint, EPFLAG_FAST_ISO);

                } else { // bugbug error here?
                    UHCD_KdPrint((1, "'WARNING: Cannot fast-iso this endpoint\n"));

                }
            }
  #endif

            urb->HcdUrbOpenEndpoint.HcdEndpoint = endpoint;
            urb->HcdUrbOpenEndpoint.ScheduleOffset = 0;

            numTDs =
                endpoint->TDCount = UHCD_GetNumTDsPerEndoint((UCHAR) (USB_ENDPOINT_TYPE_MASK &
                        urb->HcdUrbOpenEndpoint.EndpointDescriptor->bmAttributes));

            UHCD_ASSERT(TD_LIST_SIZE(numTDs) <= UHCD_LARGE_COMMON_BUFFER_SIZE);

            if (endpoint->EndpointFlags & EPFLAG_DBL_BUFFER) {
                // do the double buffer init
                ntStatus = UHCD_InitializeNoDMAEndpoint(DeviceObject,
                                                        endpoint);
            }

            if (NT_SUCCESS(ntStatus)) {

                endpoint->MaxRequests =
                    MAX_REQUESTS(urb->HcdUrbOpenEndpoint.EndpointDescriptor,
                                 endpoint->EndpointFlags);

                UHCD_KdPrint((2, "'MaxRequests = %d\n", endpoint->MaxRequests));

                // init transfer sequence numbers
                endpoint->NextXferId = 0;
                endpoint->CurrentXferId = 0;

                if (endpoint->EndpointFlags & EPFLAG_FAST_ISO) {
                    // do the double buffer init
                    UHCD_KdPrint((1, "'Using Fast ISO for Endpoint\n"));

                    ntStatus = UHCD_FinishInitializeEndpoint(DeviceObject,
                                                             endpoint,
                                                             urb->HcdUrbOpenEndpoint.EndpointDescriptor,
                                                             urb);

                    if (NT_SUCCESS(ntStatus)) {
                        ntStatus = UHCD_InitializeFastIsoEndpoint(DeviceObject,
                                                                  endpoint);
                    }
                } else {

                    for (cnt=0; cnt< endpoint->MaxRequests; cnt++) {

                        if ((UHCD_AllocateHardwareDescriptors(DeviceObject,
                                                              &endpoint->HardwareDescriptorList[cnt],
                                                              endpoint->TDCount))) {
                                PUCHAR descriptors;
                                ULONG i;

                                descriptors = endpoint->HardwareDescriptorList[cnt]->MemoryDescriptor->VirtualAddress;

                                // Initialize the queue head for this endpoint
                                // use the TD on the first list
                                if (cnt == 0) {

                                    endpoint->QueueHead = (PHW_QUEUE_HEAD) descriptors;

                                    // save our endpoint in the QueueHead

                                    endpoint->QueueHead->Endpoint = endpoint;
                                }

                                //
                                // the TDs we'll need to service this endpoint
                                //

                                endpoint->SlotTDList[cnt] = (PUHCD_TD_LIST) (descriptors + sizeof(HW_QUEUE_HEAD));

                                // BUGBUG possibly move this to allocate TD code
                                for (i=0; i<= endpoint->TDCount; i++) {
                                    // one time init stuff
                                    // for isoch TDs.

                                    endpoint->SlotTDList[cnt]->TDs[i].Frame = 0;
                                    endpoint->SlotTDList[cnt]->TDs[i].Urb = NULL;
                                }

                        } else {

                            RETHEAP(endpoint);
                            endpoint = NULL;
                            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
                            break;
                        }
                    }  // for

                    // point to first list
                    if (endpoint) {
                        endpoint->TDList = endpoint->SlotTDList[0];
                    }
                }
            }

        } else {
            // failed to alloc endpoint structure
            UHCD_KdTrap(("UHCD failed to alloc endpoint structure\n"));
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        }

#if DBG
        UHCD_KdPrint((2, "'Open Endpoint, max transfer size  0x%x \n",
                      urb->HcdUrbOpenEndpoint.MaxTransferSize));
//        transfers >64k are valid

//        if (urb->HcdUrbOpenEndpoint.MaxTransferSize >
//            1024*64) {
//            // bigger than 64k, probably a bug.
//            UHCD_KdPrint((2, "'Open Endpoint, max transfer size  0x%x \n",
//                urb->HcdUrbOpenEndpoint.MaxTransferSize));
//            TEST_TRAP();
//        }
#endif

        if (NT_SUCCESS(ntStatus)) {
            //
            // take the opportunity to grow our pool
            // in necessary
            //
            // BUGBUG possibly use max_packet size as a hint
            UHCD_MoreCommonBuffers(DeviceObject);

            URB_HEADER(urb).Status = UHCD_STATUS_PENDING_STARTIO;
            ntStatus = STATUS_PENDING;
            UHCD_KdPrint((2, "'Queue Irp To StartIo\n"));

            Irp->IoStatus.Status = ntStatus;
            Irp->IoStatus.Information = 0;
            IoMarkIrpPending(Irp);



            IoStartPacket(DeviceObject,
                          Irp,
                          0,
                          UHCD_StartIoCancel);
        } else {
            URB_HEADER(urb).Status = USBD_STATUS_NO_MEMORY;
            UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
        }
        break;


    case URB_FUNCTION_CONTROL_TRANSFER:
    case URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER:
        // this will activate the rolover interrupts
        deviceExtension->XferIdleTime = 0;

    case URB_FUNCTION_HCD_CLOSE_ENDPOINT:


        if (URB_HEADER(urb).Function == URB_FUNCTION_HCD_CLOSE_ENDPOINT) {
            endpoint = urb->HcdUrbCloseEndpoint.HcdEndpoint;
            ASSERT_ENDPOINT(endpoint);
        } else {
//#if DBG
//            UHCD_KdPrint((2, "'originalTransfer Buffer = 0x%x \n",
//                urb->HcdUrbCommonTransfer.TransferBuffer));
//            // Trash the TransferBuffer field - we only use MDLs
//            urb->HcdUrbCommonTransfer.TransferBuffer = (PVOID)-1;
//#endif
            // allocate some working space and attach it to
            // this urb

            endpoint = HCD_AREA(urb).HcdEndpoint;
            ASSERT_ENDPOINT(endpoint);

            if (urb->HcdUrbCommonTransfer.TransferBufferLength >
                endpoint->MaxTransferSize) {
                ntStatus = STATUS_INVALID_PARAMETER;
                URB_HEADER(urb).Status = USBD_STATUS_INVALID_PARAMETER;
                UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
                break;
            }

            siz = COMPUTE_HCD_EXTENSION_SIZE(urb);

            HCD_AREA(urb).HcdExtension =
                GETHEAP(NonPagedPool, siz);

            if (HCD_AREA(urb).HcdExtension == NULL) {
                ntStatus = STATUS_INSUFFICIENT_RESOURCES;
                URB_HEADER(urb).Status = USBD_STATUS_NO_MEMORY;
                UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
                break;
            }

            RtlZeroMemory(HCD_AREA(urb).HcdExtension, siz);
        }

#ifdef ROOT_HUB

        if (endpoint->EndpointFlags & EPFLAG_ROOT_HUB) {
            // These routines will either complete
            // the irp or mark it pending.
            switch (URB_HEADER(urb).Function) {
            case URB_FUNCTION_CONTROL_TRANSFER:
                ntStatus = UHCD_RootHub_ControlTransfer(DeviceObject,
                                                        Irp,
                                                        urb);

                // note: URB and IRP may be gone
                break;
            case URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER:
                ntStatus = UHCD_RootHub_InterruptTransfer(DeviceObject,
                                                          Irp,
                                                          urb);
                // note: URB and IRP may be gone
                break;
            case URB_FUNCTION_HCD_CLOSE_ENDPOINT:
                ntStatus = UHCD_RootHub_CloseEndpoint(DeviceObject,
                                                      Irp,
                                                      urb);
                break;
            default:
                //BUGBUG could just complete it with an error
                //here
                // this is probably a bug in the hub driver
                UHCD_KdTrap(("Bogus transfer request to root hub\n"));
            }
            break;
        }
#endif
        URB_HEADER(urb).Status = UHCD_STATUS_PENDING_STARTIO;
        ntStatus = STATUS_PENDING;
        UHCD_KdPrint((2, "'Queue Irp To StartIo\n"));

        Irp->IoStatus.Status = ntStatus;
        Irp->IoStatus.Information = 0;
        IoMarkIrpPending(Irp);

        IoStartPacket(DeviceObject,
                      Irp,
                      0,
                      UHCD_StartIoCancel);

        break;

    case URB_FUNCTION_ISOCH_TRANSFER:

        //
        // validate max transfer size

        // this will activate the rollover interrupts
        UHCD_WakeIdle(DeviceObject);

        endpoint = HCD_AREA(urb).HcdEndpoint;
        ASSERT_ENDPOINT(endpoint);
        // don't ref TDList
        endpoint->TDList = (PVOID) -1;

        if (urb->HcdUrbCommonTransfer.TransferBufferLength >
            endpoint->MaxTransferSize) {
            ntStatus = STATUS_INVALID_PARAMETER;
            URB_HEADER(urb).Status = USBD_STATUS_INVALID_PARAMETER;
            UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
            break;
        }

        // allocate some working space and attach it to
        // this urb

        siz = COMPUTE_HCD_EXTENSION_SIZE(urb);

        HCD_AREA(urb).HcdExtension =
            GETHEAP(NonPagedPool, siz);

        if (HCD_AREA(urb).HcdExtension == NULL) {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            URB_HEADER(urb).Status = USBD_STATUS_NO_MEMORY;
            UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
            break;
        }

        RtlZeroMemory(HCD_AREA(urb).HcdExtension, siz);

        if (HCD_AREA(urb).HcdExtension == NULL) {
            UHCD_KdBreak((2, "'Unable to allocate working space for isoch transfer\n"));
            URB_HEADER(urb).Status = USBD_STATUS_NO_MEMORY;
            UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
            break;
        }

        URB_HEADER(urb).Status = UHCD_STATUS_PENDING_STARTIO;
        ntStatus = STATUS_PENDING;
        UHCD_KdPrint((2, "'Queue Irp To StartIo\n"));

        Irp->IoStatus.Status = ntStatus;
        Irp->IoStatus.Information = 0;
        IoMarkIrpPending(Irp);
#if DBG
        {
            UHCD_KdPrint((2, "'cf = %x",
                UHCD_GetCurrentFrame(DeviceObject)));
        }
#endif


        if (endpoint->EndpointFlags & EPFLAG_FAST_ISO) {
            KIRQL irql;

            KeRaiseIrql(DISPATCH_LEVEL, &irql);
            UHCD_Transfer_StartIo(DeviceObject, Irp);
            KeLowerIrql(irql);

        } else {
        IoStartPacket(DeviceObject,
                      Irp,
                      0,
                      UHCD_StartIoCancel);
        }
        break;

    case URB_FUNCTION_GET_CURRENT_FRAME_NUMBER:
        // this will activate the rollover interrupts
        UHCD_WakeIdle(DeviceObject);

        urb->UrbGetCurrentFrameNumber.FrameNumber =
            UHCD_GetCurrentFrame(DeviceObject);
        LOGENTRY(LOG_MISC, 'gcfR',
            Irp, urb, urb->UrbGetCurrentFrameNumber.FrameNumber);
        URB_HEADER(urb).Status = USBD_STATUS_SUCCESS;
        UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
        break;

    case URB_FUNCTION_HCD_GET_ENDPOINT_STATE:
    case URB_FUNCTION_HCD_SET_ENDPOINT_STATE:

        URB_HEADER(urb).Status = UHCD_STATUS_PENDING_STARTIO;
        ntStatus = STATUS_PENDING;
        UHCD_KdPrint((2, "'Queue Irp To StartIo\n"));

        Irp->IoStatus.Status = ntStatus;
        Irp->IoStatus.Information = 0;
        IoMarkIrpPending(Irp);

        IoStartPacket(DeviceObject,
                      Irp,
                      0,
                      UHCD_StartIoCancel);
        break;

    case URB_FUNCTION_HCD_ABORT_ENDPOINT:

        endpoint = urb->HcdUrbAbortEndpoint.HcdEndpoint;

        ASSERT_ENDPOINT(endpoint);

        //
        // Mark the endpoint so that we abort all the transfers the
        // next time we process it.
        //

        SET_EPFLAG(endpoint,
                   EPFLAG_ABORT_PENDING_TRANSFERS | EPFLAG_ABORT_ACTIVE_TRANSFERS);

        URB_HEADER(urb).Status = UHCD_STATUS_PENDING_STARTIO;
        ntStatus = STATUS_PENDING;
        UHCD_KdPrint((2, "'Queue Irp To StartIo\n"));

        Irp->IoStatus.Status = ntStatus;
        Irp->IoStatus.Information = 0;
        IoMarkIrpPending(Irp);

        IoStartPacket(DeviceObject,
                      Irp,
                      0,
                      UHCD_StartIoCancel);
        break;

     case URB_FUNCTION_SET_FRAME_LENGTH:
        {
        CHAR sofModify;

        //get the current value

        sofModify = (CHAR) (READ_PORT_UCHAR(SOF_MODIFY_REG(deviceExtension)));
        sofModify += (CHAR) urb->UrbSetFrameLength.FrameLengthDelta;
        WRITE_PORT_UCHAR(SOF_MODIFY_REG(deviceExtension),  (UCHAR) sofModify);

        URB_HEADER(urb).Status = USBD_STATUS_SUCCESS;
        UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
        UHCD_KdPrint((2, "'Irp Completed in dispatch\n"));
        }
        break;

     case URB_FUNCTION_GET_FRAME_LENGTH:
        {
        CHAR sofModify;

        //get the current value

        sofModify = (CHAR) (READ_PORT_UCHAR(SOF_MODIFY_REG(deviceExtension)));
        urb->UrbGetFrameLength.FrameNumber = UHCD_GetCurrentFrame(DeviceObject);
        urb->UrbGetFrameLength.FrameLength = UHCD_12MHZ_SOF + sofModify;


        URB_HEADER(urb).Status = USBD_STATUS_SUCCESS;
        UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
        UHCD_KdPrint((2, "'Irp Completed in dispatch\n"));
        }
        break;

    default:
        UHCD_KdPrint((2, "'UHCD_URB_Dispatch -- invalid URB function (%x)\n",
            URB_HEADER(urb).Function));
        URB_HEADER(urb).Status = USBD_STATUS_INVALID_URB_FUNCTION;
        ntStatus = STATUS_INVALID_PARAMETER;
        UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);
    }

    UHCD_KdPrint((2, "'exit UHCD_URB_Dispatch (%x)\n", ntStatus));

    return ntStatus;
}


VOID
UHCD_StartIo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    Process URBs from the startIo routine.

Arguments:

    DeviceObject - pointer to a device object

    Irp - pointer to an I/O Request Packet

Return Value:


--*/
{
    PHCD_URB urb;
    KIRQL irql;

    UHCD_KdPrint((2, "'enter UHCD_StartIo\n"));

    //
    // see if the Irp was canceled
    //

    urb = URB_FROM_IRP(Irp);

    IoAcquireCancelSpinLock(&irql);

    IoSetCancelRoutine(Irp, NULL);

    if (Irp->Cancel) {
        TEST_TRAP();

        Irp->IoStatus.Status = STATUS_CANCELLED;

        IoReleaseCancelSpinLock(irql);

        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        IoStartNextPacket(DeviceObject, FALSE);

    } else {

        if (UHCD_IS_TRANSFER(urb)) {
            IoSetCancelRoutine(Irp, UHCD_TransferCancel);
        } else {
            IoSetCancelRoutine(Irp, NULL);
        }

        IoReleaseCancelSpinLock(irql);

        switch(URB_HEADER(urb).Function) {
        case URB_FUNCTION_HCD_OPEN_ENDPOINT:
            UHCD_OpenEndpoint_StartIo(DeviceObject, Irp);
            break;

        case URB_FUNCTION_HCD_CLOSE_ENDPOINT:
            LOGENTRY(LOG_MISC, 'CLEP',
                     (PUHCD_ENDPOINT)urb->HcdUrbAbortEndpoint.HcdEndpoint, 0,
                     0);
            UHCD_CloseEndpoint_StartIo(DeviceObject, Irp);
            break;

        case URB_FUNCTION_ISOCH_TRANSFER:
        case URB_FUNCTION_CONTROL_TRANSFER:
        case URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER:
            UHCD_Transfer_StartIo(DeviceObject, Irp);
            break;

        case URB_FUNCTION_HCD_ABORT_ENDPOINT:

            // walk the pending transfer list, since the
            // enpoint is halted no new transfers will
            // be dequeued by EndpointWorker and the only
            // way to clear the HALT is thru
            // GetSetEndpointState_StartIo so it is safe to
            // mess with the list.

            {
                PIRP irp;
                PHCD_URB urbtmp;
                PUHCD_ENDPOINT endpoint;
                PLIST_ENTRY listEntry;
                KIRQL irql;

                endpoint = (PUHCD_ENDPOINT) urb->HcdUrbAbortEndpoint.HcdEndpoint;
                ASSERT_ENDPOINT(endpoint);

                UHCD_KdPrint((2, "'Aborting endpoint %x flags = %x\n",
                    endpoint, endpoint->EndpointFlags));
                LOGENTRY(LOG_MISC, 'ABRP', endpoint, 0, 0);

                LOCK_ENDPOINT_PENDING_LIST(endpoint, irql, 'lck4');

                while (!IsListEmpty(&endpoint->PendingTransferList)) {

                    listEntry = RemoveHeadList(&endpoint->PendingTransferList);
                    urbtmp = (PHCD_URB) CONTAINING_RECORD(listEntry,
                                                          struct _URB_HCD_COMMON_TRANSFER,
                                                          hca.HcdListEntry);

                    UNLOCK_ENDPOINT_PENDING_LIST(endpoint, irql, 'ulk4');

                    UHCD_KdPrint((2, "'Aborting urb = %x\n", urbtmp));

                    URB_HEADER(urbtmp).Status = USBD_STATUS_CANCELED;

                    //
                    // complete the request
                    //

                    irp =  HCD_AREA(urbtmp).HcdIrp;
                    LOGENTRY(LOG_MISC, 'ABRc', endpoint, irp, 0);

                    UHCD_CompleteIrp(DeviceObject,
                                     irp,
                                     STATUS_CANCELLED,
                                     0,
                                     urbtmp);

                    LOCK_ENDPOINT_PENDING_LIST(endpoint, irql, 'lck5');
                }

                UNLOCK_ENDPOINT_PENDING_LIST(endpoint, irql, 'ulk5');

                // do cleanup for fast iso
                if (endpoint->EndpointFlags & EPFLAG_FAST_ISO) {
                    ULONG f;

                    for (f=0; f < FRAME_LIST_SIZE; f++) {
                        UHCD_CleanupFastIsoTD(DeviceObject,
                                              endpoint,
                                              f,
                                              FALSE);
                    }
                }

            }

            //
            // we have already set the abortPendingTransfers and
            // abortActiveTransfers flags in the dispatch
            // routine.  Now we need to request an interrupt
            // so that any active transfers will be cleaned
            // up.
            //
            UHCD_RequestInterrupt(DeviceObject, -2);

            IoStartNextPacket(DeviceObject, FALSE);
            URB_HEADER(urb).Status = USBD_STATUS_SUCCESS;
            UHCD_CompleteIrp(DeviceObject, Irp, STATUS_SUCCESS, 0, NULL);

            UHCD_KdPrint((2, "'UHCD Abort Endpoint Request Complete\n"));
            break;

        case URB_FUNCTION_HCD_GET_ENDPOINT_STATE:
        case URB_FUNCTION_HCD_SET_ENDPOINT_STATE:
            UHCD_GetSetEndpointState_StartIo(DeviceObject, Irp);
            break;

        default:
            UHCD_KdPrint((2, "'UHCD_URB_StartIo -- invalid URB function (%x)\n", URB_HEADER(urb).Function));
            URB_HEADER(urb).Status = USBD_STATUS_INVALID_URB_FUNCTION;
            UHCD_CompleteIrp(DeviceObject, Irp, STATUS_SUCCESS, 0, NULL);
        }

    }
    UHCD_KdPrint((2, "'exit UHCD_StartIo\n"));
}


VOID
UHCD_OpenEndpoint_StartIo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    Create a UHCD endpoint, this function is called from UHCD_StartIo to
    create a new endpoint structure.

Arguments:

    DeviceObject - pointer to a device object

    Irp - pointer to an I/O Request Packet

Return Value:


--*/
{
    PHCD_URB urb;
    PDEVICE_EXTENSION deviceExtension;
    PUHCD_ENDPOINT endpoint;
    PUSB_ENDPOINT_DESCRIPTOR endpointDescriptor;
    KIRQL irql;
    NTSTATUS ntStatus;
    BOOLEAN haveBW;
    ULONG offset;


    UHCD_KdPrint((2, "'enter UHCD_OpenEndpoint_StartIo\n"));

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    //
    // extract a pointer to the URB
    //

    urb = URB_FROM_IRP(Irp);

    //
    // make sure the length of the urb is what we expect
    //

    if (urb->HcdUrbOpenEndpoint.Length != sizeof(struct _URB_HCD_OPEN_ENDPOINT)) {
        URB_HEADER(urb).Status = USBD_STATUS_INVALID_PARAMETER;
        ntStatus = STATUS_INVALID_PARAMETER;
        goto UHCD_OpenEndpoint_StartIo_Done;
    }

    //
    // information about the endpoint comes from the USB endpoint
    // descriptor passed in the URB.
    //

    endpointDescriptor = urb->HcdUrbOpenEndpoint.EndpointDescriptor;
    URB_HEADER(urb).Status = USBD_STATUS_SUCCESS;

    //
    // Allocate resources for the endpoint, this includes an endpoint
    // handle that will be passed to us in subsequent transfer requests
    //

    ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    endpoint = urb->HcdUrbOpenEndpoint.HcdEndpoint;

    UHCD_ASSERT(endpoint != NULL);
    ASSERT_ENDPOINT(endpoint);

    if (endpoint) {

        //
        // initialize endpoint structures, state variables, queue head, ...
        //

        // bugbug -- may not need to call this for fastIiso
        ntStatus = UHCD_FinishInitializeEndpoint(DeviceObject,
                                                 endpoint,
                                                 endpointDescriptor,
                                                 urb);

        if (!NT_SUCCESS(ntStatus)) {
            RETHEAP(endpoint);
            goto UHCD_OpenEndpoint_StartIo_Done;
        }

        UHCD_KdPrint((2, "'Open Endpoint:\n"));
        UHCD_KdPrint((2, "'Type = (%d) \n", endpoint->Type));
#if DBG
        switch (endpoint->Type) {
        case USB_ENDPOINT_TYPE_CONTROL:
            UHCD_KdPrint((2, "'-control\n"));
            break;
        case USB_ENDPOINT_TYPE_ISOCHRONOUS:
            UHCD_KdPrint((2, "'-iso\n"));
            break;
        case USB_ENDPOINT_TYPE_BULK:
            UHCD_KdPrint((2, "'-bulk\n"));
            break;
        case USB_ENDPOINT_TYPE_INTERRUPT:
            UHCD_KdPrint((2, "'-interrupt\n"));
        }

        if (USB_ENDPOINT_DIRECTION_IN(endpointDescriptor->bEndpointAddress)) {
            UHCD_KdPrint((2, "'IN\n"));
        } else {
            UHCD_KdPrint((2, "'OUT\n"));
        }
#endif
        UHCD_KdPrint((2, "'EP Address = %d\n", endpoint->EndpointAddress));
        UHCD_KdPrint((2, "'DEV Address = %d\n", endpoint->DeviceAddress));
        UHCD_KdPrint((2, "'MaxPacket = %d\n", endpoint->MaxPacketSize));
        UHCD_KdPrint((2, "'Interval: Requested = %d, Selected = %d\n",  endpointDescriptor->bInterval,
                            endpoint->Interval));


        //
        // Now attempt to allocate the bandwidth we'll need to
        // open this endpoint
        //

        if (endpoint->Type == USB_ENDPOINT_TYPE_INTERRUPT) {

            ULONG i;

            // check bw available in all locations and
            // pick the least loaded frame
            offset = 0;
            for (i=0; i< endpoint->Interval; i++) {
                if (deviceExtension->BwTable[i] > deviceExtension->BwTable[offset]) {
                    offset = i;
                }
            }

            haveBW = UHCD_AllocateBandwidth(DeviceObject,
                                            endpoint,
                                            offset);
            if (!haveBW) {
                //
                // could not use the least loaded frame, just grab the
                // first frame we can fit in.
                //
                for (offset=0; offset<endpoint->Interval; offset++) {
                    haveBW = UHCD_AllocateBandwidth(DeviceObject,
                                                endpoint,
                                                offset);
                    if (haveBW) {
                        UHCD_KdPrint((2, "'using offset %d\n", offset));
                        break;
                    }
                }
            }

            urb->HcdUrbOpenEndpoint.ScheduleOffset = offset;

        } else {
            offset = 0;
            haveBW = UHCD_AllocateBandwidth(DeviceObject,
                                            endpoint,
                                            offset);
        }

        if (!haveBW) {   // no offset
            ULONG cnt;
            //
            // insufficient bandwidth to open this
            // endpoint.
            //

            URB_HEADER(urb).Status = USBD_STATUS_NO_BANDWIDTH;
            UHCD_KdPrint((0, "'warning: no bandwidth for endpoint\n"));

            for (cnt=0; cnt< endpoint->MaxRequests; cnt++) {

                UHCD_FreeHardwareDescriptors(DeviceObject,
                    endpoint->HardwareDescriptorList[cnt]);
            }

            RETHEAP(endpoint);

            goto UHCD_OpenEndpoint_StartIo_Done;
        }

        if (endpoint->EndpointFlags & EPFLAG_FAST_ISO) {
            goto UHCD_OpenEndpoint_StartIo_Done;
        }

        //
        // put the endpoint in to our list of active endpoints
        //
        // BUGBUG may want to wait for the first transfer to
        // do this.
        //

        LOCK_ENDPOINT_LIST(deviceExtension, irql);

        if (deviceExtension->EndpointListBusy) {
            // if the endpoint list is busy we have to put the endpoint
            // on a lookaside list so that the ISRDPC can add it later
            InsertHeadList(&deviceExtension->EndpointLookAsideList, &endpoint->ListEntry);
        } else {
            InsertHeadList(&deviceExtension->EndpointList, &endpoint->ListEntry);
        }

        UNLOCK_ENDPOINT_LIST(deviceExtension, irql);

        //
        // Put this Queue Head in the Schedule.
        //

        //
        // This routine will insert the queue head in the proper place
        // in the schedule and add the endpoint to the endpoint list
        // so that completed transfers will be detected.
        //

        KeAcquireSpinLock(&deviceExtension->HcScheduleSpin, &irql);

        UHCD_InsertQueueHeadInSchedule(DeviceObject,
                                       endpoint,
                                       endpoint->QueueHead,
                                       offset); // no offset

        // clear the idle flag just in case it got set
        CLR_EPFLAG(endpoint, EPFLAG_IDLE);

        KeReleaseSpinLock(&deviceExtension->HcScheduleSpin, irql);

        //
        // return the endpoint handle
        //

        urb->HcdUrbOpenEndpoint.HcdEndpoint = endpoint;

        ntStatus = STATUS_SUCCESS;

    } /* if endpoint */

    //
    // Complete the IRP, status is in the status field of the URB
    //

    UHCD_KdPrint((2, "'exit UHCD_OpenEndpoint_StartIo (URB STATUS = %x)\n", URB_HEADER(urb).Status ));

UHCD_OpenEndpoint_StartIo_Done:

#if DBG
    //
    // sanity check our buffer pools
    //
//    UHCD_BufferPoolCheck(DeviceObject);
#endif

    IoStartNextPacket(DeviceObject, FALSE);
    UHCD_CompleteIrp(DeviceObject, Irp, ntStatus, 0, NULL);

    return;
}


VOID
UHCD_CloseEndpoint_StartIo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    Free a UHCD 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:


--*/
{
    PHCD_URB urb;
    PUHCD_ENDPOINT endpoint;
    BOOLEAN outstandingTransfers = FALSE;
    PDEVICE_EXTENSION deviceExtension;
    ULONG i;
    KIRQL irql;
    BOOLEAN removed;

    UHCD_KdPrint((2, "'enter UHCD_CloseEndpoint_StartIo\n"));

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    urb = URB_FROM_IRP(Irp);

    endpoint = urb->HcdUrbCloseEndpoint.HcdEndpoint;
    ASSERT_ENDPOINT(endpoint);

    if (endpoint->EndpointFlags & EPFLAG_FAST_ISO) {

        UHCD_UnInitializeFastIsoEndpoint(DeviceObject,
                                         endpoint);

        UHCD_FreeBandwidth(DeviceObject, endpoint, endpoint->Offset);

    }

    //
    // if there are any pending transfers fail this request
    //

    LOCK_ENDPOINT_PENDING_LIST(endpoint, irql, 'lck4');

    //
    // Do we have any transfers pending?
    //

    outstandingTransfers = !IsListEmpty(&endpoint->PendingTransferList);

    for (i=0; !outstandingTransfers && i < endpoint->MaxRequests; i++) {

        //
        // no outstanding transfers in the queue, check the active list
        // -- if some transfers get retired while we walk the list that
        //    is OK.
        //

        if (endpoint->ActiveTransfers[i] != NULL) {
            outstandingTransfers = TRUE;
        }
    }

    UNLOCK_ENDPOINT_PENDING_LIST(endpoint, irql, 'ulk4');

    if (outstandingTransfers) {

        //
        // If we have outstanding transfers then we fail the
        // request
        //

        URB_HEADER(urb).Status = USBD_STATUS_ERROR_BUSY;

    } else {

        //
        // Remove the endpoint Queue from the schedule
        //

        KeAcquireSpinLock(&deviceExtension->HcScheduleSpin, &irql);

        removed = UHCD_RemoveQueueHeadFromSchedule(DeviceObject,
                                                   endpoint,
                                                   endpoint->QueueHead,
                                                   TRUE);

        KeReleaseSpinLock(&deviceExtension->HcScheduleSpin, irql);

        // stop 'NoDMA' transfers
        if (endpoint->EndpointFlags & EPFLAG_DBL_BUFFER) {
            UHCD_StopNoDMATransfer(DeviceObject,
                                   endpoint);
        }

        //
        // Put the endpoint on a queue to be freed at after
        // the next frame has executed
        //

        // At this point the hardware links have been updated to remove this
        // endpoint.
        //
        // we note the frame when it is safe to retire the endpoint so that
        // the queue head may be freed safely by the ISR DPC routine next time
        // we take an interrupt.
        endpoint->FrameToClose = UHCD_GetCurrentFrame(DeviceObject)+2;

        // BUGBUG this needs to be protected from the ISR DPC
        // routine where these things are actually freed.
        // queue it to be released
        if (removed) {
            InsertTailList(&deviceExtension->ClosedEndpointList, &endpoint->ListEntry);
        }

        URB_HEADER(urb).Status = USBD_STATUS_SUCCESS;
    }

    IoStartNextPacket(DeviceObject, FALSE);
    UHCD_CompleteIrp(DeviceObject, Irp, STATUS_SUCCESS, 0, NULL);


    UHCD_KdPrint((2, "'exit UHCD_CloseEndpoint_StartIo\n"));
}


VOID
UHCD_InsertQueueHeadInSchedule(
    IN PDEVICE_OBJECT DeviceObject,
    IN PUHCD_ENDPOINT Endpoint,
    IN PHW_QUEUE_HEAD QueueHead,
    IN ULONG Offset
    )
/*++

Routine Description:

    Inserts an initialized queue head into the schedule


    Queue head schedule looks like this:

    PQH = persistant queue head
    CQH = control Queue Head
    IQH = Interrupt Queue Head
    BQH = Bulk Queue Head

    The persistant queue head software links
    look like this:

               |>---->---->-|
               | <-    <-   | prev
              BQH   PQH    CQH
               |  ->    ->  | next
               |<----<----<-|


    IQH->IQH->PQH

    Hardware links look like this:

    PQH->CQH->BQH->|    (reclimaton on)
               |-<-|

         or

    PQH->CQH->BQH->|    (reclimation off)
              |<-T-|  (T BIT SET)



    Iso/Interrupt hardware links:

    ISO->IQH->PQH

Arguments:

    DeviceObject - device object for this controller.

    Endpoint - endpoint this Queue Head belongs to.

    QueueHead - queue head to insert in schedule.

Return Value:


--*/
{
    PDEVICE_EXTENSION deviceExtension;
    PHW_QUEUE_HEAD persistantQueueHead, queueHeadForFrame,
        nextQueueHead, prevQueueHead;
#ifdef RECLAIM_BW
    PHW_QUEUE_HEAD firstBulkQueueHead;
#endif
    ULONG i, interval;
    BOOLEAN fixFrameList = FALSE;

    UHCD_ASSERT(!(Endpoint->EndpointFlags & EPFLAG_ED_IN_SCHEDULE));

    UHCD_KdPrint((2, "'enter InsertQueueHeadInSchedule\n"));

    ASSERT_ENDPOINT(Endpoint);
    ASSERT_QUEUE_HEAD(QueueHead);

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    persistantQueueHead = deviceExtension->PersistantQueueHead;


    switch(Endpoint->Type) {
    case USB_ENDPOINT_TYPE_CONTROL:
        LOGENTRY(LOG_MISC, 'Cep+', Endpoint, 0, DeviceObject);

        //
        // before
        //
        //   a     b
        //  -PQH<->CQH-
        //  |---<->---|
        //
        // after
        //
        //   a                b
        //  -PQH<->CQH(new)<->CQH-
        //  |-------<->----------|
        //

        // insert in list
        QueueHead->Next = persistantQueueHead->Next;  //point to b
        QueueHead->Prev = persistantQueueHead;        //point to a

        QueueHead->Next->Prev = QueueHead; // item b points back to new

        // PQH points to the new element
        QueueHead->Prev->Next = QueueHead; // item a points to new

        // Fix up hardware links
        QueueHead->HW_HLink = persistantQueueHead->HW_HLink;

        // we are in the chain just after the Persistant Queue Head
        // this line activates the QH
        persistantQueueHead->HW_HLink =
            QueueHead->PhysicalAddress;
        break;

    case USB_ENDPOINT_TYPE_INTERRUPT:
        LOGENTRY(LOG_MISC, 'Iep+', Endpoint, QueueHead, DeviceObject);
        QueueHead->Next = 0;
        QueueHead->Prev = 0;

        interval = Endpoint->Interval;
        UHCD_ASSERT(Offset < interval);

        UHCD_ASSERT(interval != 0);

        LOGENTRY(LOG_MISC, 'Iep+',
                 Endpoint,
                 interval,
                 &deviceExtension->InterruptSchedule[0]);

        // MAX_INTERVAL is the maximum polling interval we support in UHCD (power of 2).
        // the requested polling intervals is always rounded to the next lowest power of 2.

        // We keep an array (InterruptSchedule) of MAX_INTERVAL with the virtual addresses
        // of the queue heads in the schedule. The size of InterruptSchedule is always a
        // multiple of MAX_FRAME. i.e. InterruptSchedule contains the virtual addresses of
        // queue heads for the first MAX_INTERVAL frames in the schedule.

        UHCD_ASSERT(interval<=MAX_INTERVAL);
        UHCD_ASSERT(Offset<interval);
        UHCD_ASSERT(MAX_INTERVAL % interval == 0);

        for (i=Offset; i<MAX_INTERVAL; i+=interval) {
            // select the queue head
            queueHeadForFrame = deviceExtension->InterruptSchedule[i];
            LOGENTRY(LOG_MISC, 'Iqhf',
                      0, //queueHeadForFrame->Endpoint->Interval,
                      queueHeadForFrame,
                      i);

            // find the appropriate place to add ourselves
            if (queueHeadForFrame == persistantQueueHead ||
                interval >= queueHeadForFrame->Endpoint->Interval) {

                // if the first entry is the persistant queue head or our polling
                // interval is greater or equal than this queue head then we just
                // add in front of this one, we become the root node for this
                // frame.

                LOGENTRY(LOG_MISC, 'I>SC', 0, QueueHead, i);
                deviceExtension->InterruptSchedule[i] = QueueHead;
                fixFrameList = TRUE;
                if (QueueHead->Next == 0) {
                    QueueHead->Next = queueHeadForFrame;
                    // update hardware link here
                    QueueHead->HW_HLink =  queueHeadForFrame->PhysicalAddress;
                } else {
                    // ounce the next pointer is updated
                    // it should never change
                    UHCD_ASSERT(QueueHead->Next == queueHeadForFrame);
                }

            } else {
                // if our polling interval is less than the current queue
                // head we need to insert in the proper position

                LOGENTRY(LOG_MISC, 'I<SC', queueHeadForFrame, QueueHead, i);
                nextQueueHead = queueHeadForFrame;
                do {
                    prevQueueHead = nextQueueHead;
                    nextQueueHead = nextQueueHead->Next;

                }  while (nextQueueHead != persistantQueueHead &&
                          nextQueueHead->Endpoint->Interval > interval);

                LOGENTRY(LOG_MISC, 'I<SQ', nextQueueHead,
                    QueueHead, prevQueueHead);
                UHCD_ASSERT(nextQueueHead != 0);

                // link in to the chain

                if (QueueHead->Next == 0) {
                    QueueHead->Next = nextQueueHead;
                      // update hardware link here
                    QueueHead->HW_HLink =  nextQueueHead->PhysicalAddress;
                } else {
                    UHCD_ASSERT(QueueHead->Next == nextQueueHead ||
                                nextQueueHead == QueueHead);
                }

                prevQueueHead->Next = QueueHead;

                // update hardware link here
                prevQueueHead->HW_HLink = QueueHead->PhysicalAddress;

            }

            //
            // repeat the process until we
            // have visited all possible nodes for this queue head
            //

        }

        // now update the physical frame list based on the virtual list
        // if we have modified it.

        if (fixFrameList) {
            UHCD_CopyInterruptScheduleToFrameList(DeviceObject);
        }

        break;

    case USB_ENDPOINT_TYPE_BULK:

        //
        // before
        //
        //  b      a
        //  -BQH<->PQH-
        //  |---<->---|
        //
        // after
        //
        //  b                 a
        //  -BQH<->BQH(new)<->PQH-
        //  |-------<->----------|
        //
        // We need to add this endpoint to the tail of the
        // persistant queue
        //

        LOGENTRY(LOG_MISC, 'Bep+', Endpoint, 0, DeviceObject);

        // set up queue head fields
        UHCD_KdPrint((2, "'bulk QH = %x, %x\n", QueueHead, Endpoint));

        // insert in list
        QueueHead->Next = persistantQueueHead;       // point to a
        QueueHead->Prev = persistantQueueHead->Prev; // point to b

        // first item points back to us
        QueueHead->Prev->Next = QueueHead; // item b points to new
        // head points to the element

        // select the old tail element
        prevQueueHead = persistantQueueHead->Prev; // remember b
        persistantQueueHead->Prev = QueueHead;     // item a points to new

#ifdef RECLAIM_BW

        //
        // Fix up hardware links
        //
        // BUGBUG
        // NOTE: we are only reclaiming bandwidth for bulk right
        // now.
        //

        if (deviceExtension->SteppingVersion == UHCD_B0_STEP) {

            //TEST_TRAP();
            //
            // BW reclimation, point back to the first bulk queue head
            // with no T bit set
            //

            // walk the list and find the first bulk
            // queue head

            firstBulkQueueHead = persistantQueueHead;

            do {
                PUHCD_ENDPOINT endpoint;

                endpoint = firstBulkQueueHead->Endpoint;

                if (endpoint &&
                    endpoint->Type == USB_ENDPOINT_TYPE_BULK) {
                    break;
                }

                firstBulkQueueHead = firstBulkQueueHead->Next;

            } while (firstBulkQueueHead != persistantQueueHead);

            UHCD_ASSERT(firstBulkQueueHead != persistantQueueHead);

            QueueHead->HW_HLink = firstBulkQueueHead->PhysicalAddress;

            deviceExtension->HcFlags |= HCFLAG_BWRECLIMATION_ENABLED;

        } else {
            // Fix up hardware links
            // points to the control queue head with T bit set
            QueueHead->HW_HLink = prevQueueHead->HW_HLink;
            UHCD_ASSERT(QueueHead->HW_HLink & UHCD_CF_TERMINATE);
        }
#else
        // Fix up hardware links
        // points to the control queue head with T bit set
        QueueHead->HW_HLink = prevQueueHead->HW_HLink;
        UHCD_ASSERT(QueueHead->HW_HLink & UHCD_CF_TERMINATE);
#endif

        // we are in the chain just before the PersistantQueueHead
        // this line activates the QH
        prevQueueHead->HW_HLink =
            QueueHead->PhysicalAddress;
        break;

    case USB_ENDPOINT_TYPE_ISOCHRONOUS:

        LOGENTRY(LOG_MISC, 'Sep+', Endpoint, 0, DeviceObject);
        break;

    default:
        // invalid endpoint type, probably a bug
        UHCD_KdTrap(
            ("UHCD_InsertQueueHeadInSchedule inavlid endpoint type\n"));
    }

    SET_EPFLAG(Endpoint, EPFLAG_ED_IN_SCHEDULE);

    UHCD_KdPrint((2, "'exit InsertQueueHeadInSchedule\n"));

    return;
}


BOOLEAN
UHCD_RemoveQueueHeadFromSchedule(
    IN PDEVICE_OBJECT DeviceObject,
    IN PUHCD_ENDPOINT Endpoint,
    IN PHW_QUEUE_HEAD QueueHead,
    IN BOOLEAN RemoveFromEPList
    )
/*++

Routine Description:

    Removes a queue head from the schedule

Arguments:

    DeviceObject - device object for this controller.

    Endpoint - endpoint this Queue Head belongs to.

    QueueHead - queue head to remove.

Return Value:

    returns TRUE if EP was removed from ep list

--*/
{
    PDEVICE_EXTENSION deviceExtension;
    PHW_QUEUE_HEAD persistantQueueHead, queueHeadForFrame,
        nextQueueHead, prevQueueHead;
    BOOLEAN fixFrameList = FALSE;
    ULONG i;
    HW_DESCRIPTOR_PHYSICAL_ADDRESS physicalAddress;
    KIRQL irql;
    BOOLEAN removed = FALSE;

    UHCD_KdPrint((2, "'enter RemoveQueueHeadFromSchedule\n"));

    ASSERT_ENDPOINT(Endpoint);

    if (Endpoint->EndpointFlags & EPFLAG_FAST_ISO) {
        // nothing to remove
        return FALSE;
    }

    ASSERT_QUEUE_HEAD(QueueHead);

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;
    persistantQueueHead = deviceExtension->PersistantQueueHead;

    //
    // Remove the Queue Head from the endpoint list
    //

    if (RemoveFromEPList) {
        LOCK_ENDPOINT_LIST(deviceExtension, irql);

        if (deviceExtension->EndpointListBusy) {
            // mark the entry so we can remove it later
            SET_EPFLAG(Endpoint, EPFLAG_EP_CLOSED);
        } else {
            RemoveEntryList(&Endpoint->ListEntry);
            removed = TRUE;
        }

        UNLOCK_ENDPOINT_LIST(deviceExtension, irql);
    }

    if (!(Endpoint->EndpointFlags & EPFLAG_ED_IN_SCHEDULE)) {
        LOGENTRY(LOG_MISC, 'NISc', Endpoint, 0, DeviceObject);
        return removed;
    }

    switch(Endpoint->Type) {
    case USB_ENDPOINT_TYPE_CONTROL:
    case USB_ENDPOINT_TYPE_BULK:
        // set up queue head fields

        prevQueueHead = QueueHead->Prev;
        nextQueueHead = QueueHead->Next;

        // unlink software links
        prevQueueHead->Next = QueueHead->Next;
        nextQueueHead->Prev = QueueHead->Prev;

        if ((QueueHead->HW_HLink & UHCD_DESCRIPTOR_PTR_MASK)
                 ==
            (QueueHead->PhysicalAddress & UHCD_DESCRIPTOR_PTR_MASK)) {

            //
            // Queue head points to itself, this means it is
            // the only bulk queue in the list.
            //
            // This will only happen if we have BW reclaimation
            // is enabled.
            //

            physicalAddress =
                deviceExtension->PersistantQueueHead->PhysicalAddress;
            SET_T_BIT(physicalAddress);

            UHCD_ASSERT(physicalAddress & UHCD_CF_QUEUE);
            prevQueueHead->HW_HLink = physicalAddress;

        } else {
            // Fix up hardware link
            prevQueueHead->HW_HLink = QueueHead->HW_HLink;
        }

        break;

    case USB_ENDPOINT_TYPE_INTERRUPT:

        //
        // Brute force method:
        // Walk every frame in the InterruptSchedule and update
        // any node that references this queue head.
        //

        for (i=0; i<MAX_INTERVAL; i++) {
            queueHeadForFrame = deviceExtension->InterruptSchedule[i];

            if (queueHeadForFrame == QueueHead) {
                // Queue Head was root node for this frame
                deviceExtension->InterruptSchedule[i] = QueueHead->Next;
                fixFrameList = TRUE;
            } else {
                while (queueHeadForFrame != persistantQueueHead &&
                       queueHeadForFrame->Endpoint->Interval >=
                            QueueHead->Endpoint->Interval) {
                    if (queueHeadForFrame->Next == QueueHead) {
                        // found a link to our queue head,
                        // remove it
                        queueHeadForFrame->Next = QueueHead->Next;
                        // unlink from Hardware Queue
                        queueHeadForFrame->HW_HLink = QueueHead->HW_HLink;
                    }
                    queueHeadForFrame = queueHeadForFrame->Next;
                }
            }

            if (fixFrameList) {
                UHCD_CopyInterruptScheduleToFrameList(DeviceObject);
            }
        }


        break;

    case USB_ENDPOINT_TYPE_ISOCHRONOUS:

        LOGENTRY(LOG_MISC, 'Sep-', Endpoint, 0, DeviceObject);
        break;
    default:
        // Invalid endpoint type, probably a bug
        UHCD_KdTrap(
            ("UHCD_RemoveQueueHeadFromSchedule inavlid endpoint type\n"));
    }

    CLR_EPFLAG(Endpoint,
               EPFLAG_ED_IN_SCHEDULE);

    UHCD_KdPrint((2, "'exit RemoveQueueHeadFromSchedule\n"));

    return removed;
}


VOID
UHCD_CopyInterruptScheduleToFrameList(
    IN PDEVICE_OBJECT DeviceObject
    )
/*++

Routine Description:

    Transfers the virtual interrupt schedule to the frame list

Arguments:

    DeviceObject - device object for this controller.

Return Value:

    None.

--*/
{
    PDEVICE_EXTENSION deviceExtension;
    ULONG i;

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    for (i=0; i < FRAME_LIST_SIZE; i++) {
        if (i == 0 || i == FRAME_LIST_SIZE-1)
            deviceExtension->TriggerTDList->TDs[i == 0 ? 0 : 1].HW_Link =
                deviceExtension->InterruptSchedule[i % MAX_INTERVAL]->PhysicalAddress;
        else {
            ULONG currentTdCopy, currentTd;
            PUHCD_ENDPOINT endpoint;

            currentTd =
                *( ((PULONG) (deviceExtension->FrameListVirtualAddress)+i));

            currentTdCopy =
                *( ((PULONG) (deviceExtension->FrameListCopyVirtualAddress)+i));

            endpoint = UHCD_GetLastFastIsoEndpoint(DeviceObject);

            // have fast iso?
            if (endpoint) {

                PFAST_ISO_DATA fastIsoData;
                PHW_TRANSFER_DESCRIPTOR transferDescriptor;

                // fast iso TDs are present, we will need to insert
                // the interrupt schedule after these TDs.

                fastIsoData = &endpoint->FastIsoData;
                transferDescriptor = (PHW_TRANSFER_DESCRIPTOR)
                    (fastIsoData->IsoTDListVa + (i*32));

                transferDescriptor->HW_Link =
                    deviceExtension->InterruptSchedule[i % MAX_INTERVAL]->PhysicalAddress;

            } else {
                // no fast iso -- just update the schedule
                *( ((PULONG) (deviceExtension->FrameListCopyVirtualAddress)+i) ) =
                    deviceExtension->InterruptSchedule[i % MAX_INTERVAL]->PhysicalAddress;

                // if the currentTd == the copy then we don't have any iso
                // tds in the schedule so it is safe to update the schedule directly
                if (currentTd == currentTdCopy) {
                    *( ((PULONG) (deviceExtension->FrameListVirtualAddress)+i) ) =
                        deviceExtension->InterruptSchedule[i % MAX_INTERVAL]->PhysicalAddress;
                }
            }
        }
    }
}


VOID
UHCD_StartIoCancel(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

Arguments:

Return Value:


--*/
{
    PHCD_URB urb;

    //
    // Irp has not been processed by StartIo yet
    //

    LOGENTRY(LOG_MISC, 'sioC', Irp, 0, DeviceObject);

    if (DeviceObject->CurrentIrp == Irp) {

        LOGENTRY(LOG_MISC, 'curI', Irp, 0, DeviceObject);

        IoReleaseCancelSpinLock(Irp->CancelIrql);

    } else {
        LOGENTRY(LOG_MISC, 'Ncur', Irp, 0, DeviceObject);

        if (KeRemoveEntryDeviceQueue(&DeviceObject->DeviceQueue,
                                     &Irp->Tail.Overlay.DeviceQueueEntry)) {
            LOGENTRY(LOG_MISC, 'YDVQ', Irp, 0, DeviceObject);
            TEST_TRAP();

            urb = (PHCD_URB) URB_FROM_IRP(Irp);

            IoReleaseCancelSpinLock(Irp->CancelIrql);

            while (urb) {

                URB_HEADER(urb).Status = USBD_STATUS_CANCELED;

                if (UHCD_IS_TRANSFER(urb)) {
                    urb = urb->HcdUrbCommonTransfer.UrbLink;
                } else {
                    break;
                }

            }

            Irp->IoStatus.Status = STATUS_CANCELLED;
            Irp->IoStatus.Information = 0;

            USBD_CompleteRequest(Irp,
                                 IO_NO_INCREMENT);

        } else {
            LOGENTRY(LOG_MISC, 'NDVQ', Irp, 0, DeviceObject);
            TEST_TRAP();
            IoReleaseCancelSpinLock(Irp->CancelIrql);
        }

    }
}


VOID
UHCD_GetSetEndpointState_StartIo(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp
    )
/*++

Routine Description:

    Change or report state of an endpoint

Arguments:

    DeviceObject - pointer to a device object

    Irp - pointer to an I/O Request Packet

Return Value:


--*/
{
    PUHCD_ENDPOINT endpoint;
    PHCD_URB urb;
    BOOLEAN outstandingTransfers;
    ULONG i;
    KIRQL irql;

    urb = (PHCD_URB) URB_FROM_IRP(Irp);
    endpoint = (PUHCD_ENDPOINT) urb->HcdUrbEndpointState.HcdEndpoint;

    ASSERT_ENDPOINT(endpoint);

    //
    // Do we have any transfers pending?
    //

    irql = KeGetCurrentIrql();
    LOCK_ENDPOINT_PENDING_LIST(endpoint, irql, 'lck5');

    outstandingTransfers = !IsListEmpty(&endpoint->PendingTransferList);

#if DBG
    if (outstandingTransfers) {
        UHCD_KdPrint((2, "'GET_ENDPOINT_STATE ep has pending transfers\n"));
    }
#endif

    for (i=0; !outstandingTransfers && i < endpoint->MaxRequests; i++) {

        //
        // no outstanding transfers in the queue, check the active list
        // -- if some transfers get retired while we walk the list that
        //    is OK.
        //

        if (endpoint->ActiveTransfers[i] != NULL) {
            UHCD_KdPrint((2, "'GETSET_ENDPOINT_STATE ep has active transfers\n"));
            outstandingTransfers = TRUE;
        }
    }

    UNLOCK_ENDPOINT_PENDING_LIST(endpoint, irql, 'ulk5');


    switch (urb->HcdUrbEndpointState.Function) {

    case URB_FUNCTION_HCD_GET_ENDPOINT_STATE:

        urb->HcdUrbEndpointState.HcdEndpointState = 0;

        if (endpoint->EndpointFlags & EPFLAG_HOST_HALTED) {
            UHCD_KdPrint((2, "'GET_ENDPOINT_STATE host halted\n"));
            urb->HcdUrbEndpointState.HcdEndpointState |=
                HCD_ENDPOINT_HALTED;
        }

        if (outstandingTransfers) {
            urb->HcdUrbEndpointState.HcdEndpointState |=
                HCD_ENDPOINT_TRANSFERS_QUEUED;
        }

        URB_HEADER(urb).Status = USBD_STATUS_SUCCESS;
        UHCD_KdPrint((2, "'GET_ENDPOINT_STATE state = %x\n",
            urb->HcdUrbEndpointState.HcdEndpointState));

        break;

    case URB_FUNCTION_HCD_SET_ENDPOINT_STATE:

        if (!outstandingTransfers) {
            LOGENTRY(LOG_MISC, 'cla2', endpoint, 0, 0);
            CLR_EPFLAG(endpoint,
                        EPFLAG_ABORT_PENDING_TRANSFERS |
                        EPFLAG_ABORT_ACTIVE_TRANSFERS);

        }

        UHCD_KdPrint((2, "'Set Enpoint State flags = %x\n", endpoint->EndpointFlags));
        if (endpoint->EndpointFlags & (EPFLAG_ABORT_ACTIVE_TRANSFERS |
                                       EPFLAG_ABORT_PENDING_TRANSFERS)) {
            //fail the request


            IoStartNextPacket(DeviceObject, FALSE);
            // let USBD map the error for us
            URB_HEADER(urb).Status = USBD_STATUS_ERROR_BUSY;
            UHCD_CompleteIrp(DeviceObject, Irp, STATUS_SUCCESS, 0, NULL);

            return;
        }

        if (endpoint->EndpointFlags & EPFLAG_DBL_BUFFER) {
            // stop streaming data

            UHCD_StopNoDMATransfer(DeviceObject,
                                   endpoint);
        }

        //
        // restore virgin status to the pipe
        //

        SET_EPFLAG(endpoint, EPFLAG_VIRGIN);

        //
        // set the data toggle back to 0
        //
        if (urb->HcdUrbEndpointState.HcdEndpointState &
             HCD_ENDPOINT_RESET_DATA_TOGGLE) {
            endpoint->DataToggle = 0;
        }

        if (!(urb->HcdUrbEndpointState.HcdEndpointState & HCD_ENDPOINT_HALTED)) {

            //
            // halt bit cleared, reset the endpoint.
            //
            LOGENTRY(LOG_MISC, 'cla3', endpoint, 0, 0);
            CLR_EPFLAG(endpoint,
                EPFLAG_ABORT_PENDING_TRANSFERS |
                EPFLAG_ABORT_ACTIVE_TRANSFERS |
                EPFLAG_HOST_HALTED);

            //
            // Start any transfers in the pending queue
            //

            if (endpoint->EndpointFlags & EPFLAG_DBL_BUFFER) {
                // transfers will re-start on the next interrupt
                UHCD_RequestInterrupt(DeviceObject, -2);
            } else {
                UHCD_EndpointWorker(DeviceObject, endpoint);
            }
        }

        URB_HEADER(urb).Status = USBD_STATUS_SUCCESS;

        break;

    default:
        // unknown command, probably a bug
        UHCD_KdTrap(("Bogus Endpoint state command\n"));
    }

    IoStartNextPacket(DeviceObject, FALSE);

    UHCD_CompleteIrp(DeviceObject, Irp, STATUS_SUCCESS, 0, NULL);
}


NTSTATUS
UHCD_FinishInitializeEndpoint(
    IN PDEVICE_OBJECT DeviceObject,
    IN PUHCD_ENDPOINT Endpoint,
    IN PUSB_ENDPOINT_DESCRIPTOR EndpointDescriptor,
    IN PHCD_URB Urb
    )
/*++

Routine Description:

    Change or report state of an endpoint

Arguments:

    Endpoint - endpoint structure to initilaize

    EndpointDescriptor - pointer to the USB endpoint descriptor
                for this endpoint.

    Urb - urb associated with the open request

Return Value:


--*/
{
    UCHAR tmp;
    ULONG i;
    NTSTATUS ntStatus = STATUS_SUCCESS;

    // note that some fields are already initilaized
    ASSERT_ENDPOINT(Endpoint);

    Endpoint->Type = USB_ENDPOINT_TYPE_MASK & EndpointDescriptor->bmAttributes;
    Endpoint->EndpointAddress = EndpointDescriptor->bEndpointAddress;
    Endpoint->MaxPacketSize = EndpointDescriptor->wMaxPacketSize;
    Endpoint->DeviceAddress = (UCHAR) Urb->HcdUrbOpenEndpoint.DeviceAddress;
    Endpoint->LastPacketDataToggle =
        Endpoint->DataToggle = 0;

    Endpoint->Interval = 0;
    Endpoint->IdleTime = 0;

    SET_EPFLAG(Endpoint, EPFLAG_VIRGIN);
    SET_EPFLAG(Endpoint, EPFLAG_INIT);

    Endpoint->MaxTransferSize = Urb->HcdUrbOpenEndpoint.MaxTransferSize;

    // No DMA endpoint ?
#if DBG
    if (Endpoint->EndpointFlags & EPFLAG_DBL_BUFFER) {

        // client idicates that transfers will be mostly short
        // in this case we will turn off short packet detect
        // and double buffer all transfers to reduce the overhead of
        // having to program each transfer to the hardware separately

        UHCD_KdPrint((1, "'Client requesting double buffering EP = %x\n", Endpoint));
    }
#endif


#if DBG
    if (!(Endpoint->EndpointFlags & EPFLAG_ROOT_HUB)) {
         UHCD_ASSERT(Endpoint->TDCount == UHCD_GetNumTDsPerEndoint(Endpoint->Type));
    }
#endif

    if (Urb->HcdUrbOpenEndpoint.HcdEndpointFlags & USBD_EP_FLAG_LOWSPEED) {
        SET_EPFLAG(Endpoint, EPFLAG_LOWSPEED);
    }

    if (Urb->HcdUrbOpenEndpoint.HcdEndpointFlags & USBD_EP_FLAG_NEVERHALT) {
        SET_EPFLAG(Endpoint, EPFLAG_NO_HALT);
    }

    KeInitializeSpinLock(&Endpoint->ActiveListSpin);
    KeInitializeSpinLock(&Endpoint->PendingListSpin);
#if DBG
    Endpoint->AccessPendingList = 0;
    Endpoint->AccessActiveList = 0;
#endif

    UHCD_KdPrint((2, "'MaxRequests = %d\n", Endpoint->MaxRequests));
    UHCD_ASSERT(Endpoint->MaxRequests == MAX_REQUESTS(EndpointDescriptor,
                                                      Endpoint->EndpointFlags));

    // Select the highest interval we support <= the requested interval.
    // note: an interval of zero gets a oeriod of MAX_INTERVAL

    tmp = EndpointDescriptor->bInterval;
    Endpoint->Interval = MAX_INTERVAL;

    if (EndpointDescriptor->bInterval > MAX_INTERVAL ||
        EndpointDescriptor->bInterval == 0) {
        tmp |= MAX_INTERVAL;
    }

    while ((Endpoint->Interval & tmp) == 0) {
        Endpoint->Interval >>= 1;
    }

    //
    // make sure that iso endpoints have an
    // interval of 1
    //

    if (Endpoint->Type == USB_ENDPOINT_TYPE_ISOCHRONOUS) {
        Endpoint->Interval = 1; //iso endpoints have a period of 1
    }

    InitializeListHead(&Endpoint->PendingTransferList);

    for (i=0; i<UHCD_MAX_ACTIVE_TRANSFERS; i++) {
        Endpoint->ActiveTransfers[i] = NULL;
    }

    return ntStatus;
}


USHORT
UHCD_GetNumTDsPerEndoint(
    IN UCHAR EndpointType
    )
/*++

Routine Description:

    Return the number of TDs to use for this endpoint based on type

Arguments:

Return Value:


--*/
{
    // use max TDs always for bulk to get max thru-put regardless
    // of packet size.

    // Historical Note:
    // this is a change from Win98gold and Win98se,
    // originally we only enabled this if MAX_PACKET was 64 to save
    // memory for slower devices -- but vendors bitched about it so
    // now we enable it regardless of packet size.

    if (EndpointType == USB_ENDPOINT_TYPE_ISOCHRONOUS ||
        EndpointType == USB_ENDPOINT_TYPE_BULK) {
        return MAX_TDS_PER_ENDPOINT;
    } else {
        return MIN_TDS_PER_ENDPOINT;
    }

}


VOID
UHCD_BW_Reclimation(
    IN PDEVICE_OBJECT DeviceObject,
    IN BOOLEAN Enable
    )
/*++

Routine Description:

    Turn on/off BW reclimation for the Bulk Queues

Arguments:

Return Value:

    None.

--*/
{
    PDEVICE_EXTENSION deviceExtension;
    PHW_QUEUE_HEAD persistantQueueHead, firstBulkQueueHead,
        lastBulkQueueHead;

    deviceExtension = (PDEVICE_EXTENSION) DeviceObject->DeviceExtension;

    persistantQueueHead = deviceExtension->PersistantQueueHead;

    if ((deviceExtension->HcFlags & HCFLAG_BWRECLIMATION_ENABLED)
        == Enable) {
        // no state change just return;
        return;
    }

    LOGENTRY(LOG_MISC, 'BRCL', deviceExtension, Enable, DeviceObject);

    //
    // BW reclimation, point back to the first bulk queue head
    // with no T bit set
    //

    // walk the list and find the first bulk
    // queue head

    firstBulkQueueHead = persistantQueueHead;

    do {
        PUHCD_ENDPOINT endpoint;

        endpoint = firstBulkQueueHead->Endpoint;

        if (endpoint &&
            endpoint->Type == USB_ENDPOINT_TYPE_BULK) {
            break;
        }

        firstBulkQueueHead = firstBulkQueueHead->Next;

    } while (firstBulkQueueHead != persistantQueueHead);

    if (firstBulkQueueHead != persistantQueueHead) {
        // no bulk endpoints
        PHW_QUEUE_HEAD next;
        PUHCD_ENDPOINT endpoint;

        lastBulkQueueHead = firstBulkQueueHead;

        do {
            next = lastBulkQueueHead->Next;
            endpoint = next->Endpoint;
            if (endpoint == NULL ||
                endpoint->Type != USB_ENDPOINT_TYPE_BULK) {
                break;
            }
            lastBulkQueueHead = next;
        } while (1);

        if (Enable) {
            //clear the T-bit to enable reclimation
            LOGENTRY(LOG_MISC, 'BRC+', lastBulkQueueHead, 0, DeviceObject);
            CLEAR_T_BIT(lastBulkQueueHead->HW_HLink);
            deviceExtension->HcFlags |= HCFLAG_BWRECLIMATION_ENABLED;
        } else {
            //set the T-bit to disable reclimation
            LOGENTRY(LOG_MISC, 'BRC-', lastBulkQueueHead, 0, DeviceObject);
            SET_T_BIT(lastBulkQueueHead->HW_HLink);
            deviceExtension->HcFlags &= ~HCFLAG_BWRECLIMATION_ENABLED;
        }
    }
}