/*
 * PROJECT:     ReactOS USB Port Driver
 * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
 * PURPOSE:     USBPort endpoint functions
 * COPYRIGHT:   Copyright 2017 Vadim Galyant <vgal@rambler.ru>
 */

#include "usbport.h"

#define NDEBUG
#include <debug.h>

#define NDEBUG_USBPORT_CORE
#include "usbdebug.h"

ULONG
NTAPI
USBPORT_CalculateUsbBandwidth(IN PDEVICE_OBJECT FdoDevice,
                              IN PUSBPORT_ENDPOINT Endpoint)
{
    PUSBPORT_ENDPOINT_PROPERTIES EndpointProperties;
    ULONG Bandwidth;
    ULONG Overhead;

    DPRINT("USBPORT_CalculateUsbBandwidth ... \n");

    EndpointProperties = &Endpoint->EndpointProperties;

    switch (EndpointProperties->TransferType)
    {
        case USBPORT_TRANSFER_TYPE_ISOCHRONOUS:
            Overhead = USB2_FS_ISOCHRONOUS_OVERHEAD;
            break;

        case USBPORT_TRANSFER_TYPE_INTERRUPT:
            Overhead = USB2_FS_INTERRUPT_OVERHEAD;
            break;

        default: //USBPORT_TRANSFER_TYPE_CONTROL or USBPORT_TRANSFER_TYPE_BULK
            Overhead = 0;
            break;
    }

    if (Overhead == 0)
    {
        Bandwidth = 0;
    }
    else
    {
        Bandwidth = (EndpointProperties->TotalMaxPacketSize + Overhead) * USB2_BIT_STUFFING_OVERHEAD;
    }

    if (EndpointProperties->DeviceSpeed == UsbLowSpeed)
    {
        Bandwidth *= 8;
    }

    return Bandwidth;
}

BOOLEAN
NTAPI
USBPORT_AllocateBandwidth(IN PDEVICE_OBJECT FdoDevice,
                          IN PUSBPORT_ENDPOINT Endpoint)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_ENDPOINT_PROPERTIES EndpointProperties;
    ULONG TransferType;
    ULONG TotalBusBandwidth;
    ULONG EndpointBandwidth;
    ULONG MinBandwidth;
    PULONG Bandwidth;
    ULONG MaxBandwidth = 0;
    ULONG ix;
    ULONG Offset;
    LONG ScheduleOffset = -1;
    ULONG Period;
    ULONG Factor;
    UCHAR Bit;

    DPRINT("USBPORT_AllocateBandwidth: FdoDevice - %p, Endpoint - %p\n",
           FdoDevice,
           Endpoint);

    FdoExtension = FdoDevice->DeviceExtension;
    EndpointProperties = &Endpoint->EndpointProperties;
    TransferType = EndpointProperties->TransferType;

    if (TransferType == USBPORT_TRANSFER_TYPE_BULK || 
        TransferType == USBPORT_TRANSFER_TYPE_CONTROL ||
        Endpoint->Flags & ENDPOINT_FLAG_ROOTHUB_EP0)
    {
        EndpointProperties->ScheduleOffset = 0;
        return TRUE;
    }

    TotalBusBandwidth = FdoExtension->TotalBusBandwidth;
    EndpointBandwidth = EndpointProperties->UsbBandwidth;

    Period = EndpointProperties->Period;
    ASSERT(Period != 0);
    Factor = USB2_FRAMES / Period;

    for (Offset = 0; Offset < Period; Offset++)
    {
        MinBandwidth = TotalBusBandwidth;
        Bandwidth = &FdoExtension->Bandwidth[Offset * Factor];

        for (ix = 1; *Bandwidth >= EndpointBandwidth; ix++)
        {
            MinBandwidth = min(MinBandwidth, *Bandwidth); 

            Bandwidth++;

            if (Factor <= ix)
            {
                if (MinBandwidth > MaxBandwidth)
                {
                    MaxBandwidth = MinBandwidth;
                    ScheduleOffset = Offset;

                    DPRINT("USBPORT_AllocateBandwidth: ScheduleOffset - %X\n",
                           ScheduleOffset);
                }

                break;
            }
        }
    }

    DPRINT("USBPORT_AllocateBandwidth: ScheduleOffset - %X\n", ScheduleOffset);

    if (ScheduleOffset != -1)
    {
        EndpointProperties->ScheduleOffset = ScheduleOffset;

        Bandwidth = &FdoExtension->Bandwidth[ScheduleOffset * Factor];

        for (Factor = USB2_FRAMES / Period; Factor; Factor--)
        {
            FdoExtension->Bandwidth[ScheduleOffset * Factor] -= EndpointBandwidth;
        }

        if (TransferType == USBPORT_TRANSFER_TYPE_INTERRUPT)
        {
            for (Bit = 0x80; Bit != 0; Bit >>= 1)
            {
                if ((Period & Bit) != 0)
                {
                    Period = Bit;
                    break;
                }
            }

            DPRINT("USBPORT_AllocateBandwidth: FIXME AllocatedInterrupt_XXms\n");
        }
        else
        {
            DPRINT("USBPORT_AllocateBandwidth: FIXME AllocatedIso\n");
        }
    }

    DPRINT("USBPORT_AllocateBandwidth: FIXME USBPORT_UpdateAllocatedBw\n");

    DPRINT("USBPORT_AllocateBandwidth: ScheduleOffset - %X\n", ScheduleOffset);
    return ScheduleOffset != -1;
}

VOID
NTAPI
USBPORT_FreeBandwidth(IN PDEVICE_OBJECT FdoDevice,
                      IN PUSBPORT_ENDPOINT Endpoint)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_ENDPOINT_PROPERTIES EndpointProperties;
    ULONG TransferType;
    ULONG Offset;
    ULONG EndpointBandwidth;
    ULONG Period;
    ULONG Factor;
    UCHAR Bit;

    DPRINT("USBPORT_FreeBandwidth: FdoDevice - %p, Endpoint - %p\n",
           FdoDevice,
           Endpoint);

    FdoExtension = FdoDevice->DeviceExtension;

    EndpointProperties = &Endpoint->EndpointProperties;
    TransferType = EndpointProperties->TransferType;

    if (TransferType == USBPORT_TRANSFER_TYPE_BULK ||
        TransferType == USBPORT_TRANSFER_TYPE_CONTROL ||
        (Endpoint->Flags & ENDPOINT_FLAG_ROOTHUB_EP0))
    {
        return;
    }

    Offset = Endpoint->EndpointProperties.ScheduleOffset;
    EndpointBandwidth = Endpoint->EndpointProperties.UsbBandwidth;

    Period = Endpoint->EndpointProperties.Period;
    ASSERT(Period != 0);

    for (Factor = USB2_FRAMES / Period; Factor; Factor--)
    {
        FdoExtension->Bandwidth[Offset * Factor] += EndpointBandwidth;
    }

    if (TransferType == USBPORT_TRANSFER_TYPE_INTERRUPT)
    {
        for (Bit = 0x80; Bit != 0; Bit >>= 1)
        {
            if ((Period & Bit) != 0)
            {
                Period = Bit;
                break;
            }
        }

        ASSERT(Period != 0);

        DPRINT("USBPORT_AllocateBandwidth: FIXME AllocatedInterrupt_XXms\n");
    }
    else
    {
        DPRINT("USBPORT_AllocateBandwidth: FIXME AllocatedIso\n");
    }

    DPRINT1("USBPORT_FreeBandwidth: FIXME USBPORT_UpdateAllocatedBw\n");
}

UCHAR
NTAPI
USBPORT_NormalizeHsInterval(UCHAR Interval)
{
    UCHAR interval;

    DPRINT("USBPORT_NormalizeHsInterval: Interval - %x\n", Interval);

    interval = Interval;

    if (Interval)
       interval = Interval - 1;

    if (interval > 5)
       interval = 5;

    return 1 << interval;
}

BOOLEAN
NTAPI
USBPORT_EndpointHasQueuedTransfers(IN PDEVICE_OBJECT FdoDevice,
                                   IN PUSBPORT_ENDPOINT Endpoint,
                                   IN PULONG TransferCount)
{
    PLIST_ENTRY Entry;
    PUSBPORT_TRANSFER Transfer;
    BOOLEAN Result = FALSE;

    DPRINT_CORE("USBPORT_EndpointHasQueuedTransfers: ... \n");

    KeAcquireSpinLock(&Endpoint->EndpointSpinLock, &Endpoint->EndpointOldIrql);

    if (!IsListEmpty(&Endpoint->PendingTransferList))
        Result = TRUE;

    if (!IsListEmpty(&Endpoint->TransferList))
    {
        Result = TRUE;

        if (TransferCount)
        {
            *TransferCount = 0;

            for (Entry = Endpoint->TransferList.Flink;
                 Entry && Entry != &Endpoint->TransferList;
                 Entry = Transfer->TransferLink.Flink)
            {
                Transfer = CONTAINING_RECORD(Entry,
                                             USBPORT_TRANSFER,
                                             TransferLink);

                if (Transfer->Flags & TRANSFER_FLAG_SUBMITED)
                {
                    ++*TransferCount;
                }
            }
        }
    }

    KeReleaseSpinLock(&Endpoint->EndpointSpinLock, Endpoint->EndpointOldIrql);

    return Result;
}

VOID
NTAPI
USBPORT_NukeAllEndpoints(IN PDEVICE_OBJECT FdoDevice)
{
    PUSBPORT_DEVICE_EXTENSION  FdoExtension;
    PLIST_ENTRY EndpointList;
    PUSBPORT_ENDPOINT Endpoint;
    KIRQL OldIrql;

    DPRINT("USBPORT_NukeAllEndpoints \n");

    FdoExtension = FdoDevice->DeviceExtension;

    KeAcquireSpinLock(&FdoExtension->EndpointListSpinLock, &OldIrql);

    EndpointList = FdoExtension->EndpointList.Flink;

    while (EndpointList && (EndpointList != &FdoExtension->EndpointList))
    {
        Endpoint = CONTAINING_RECORD(EndpointList,
                                     USBPORT_ENDPOINT,
                                     EndpointLink);

        if (!(Endpoint->Flags & ENDPOINT_FLAG_ROOTHUB_EP0))
            Endpoint->Flags |= ENDPOINT_FLAG_NUKE;

        EndpointList = Endpoint->EndpointLink.Flink;
    }

    KeReleaseSpinLock(&FdoExtension->EndpointListSpinLock, OldIrql);
}

ULONG 
NTAPI
USBPORT_GetEndpointState(IN PUSBPORT_ENDPOINT Endpoint)
{
    ULONG State;

    //DPRINT("USBPORT_GetEndpointState \n");

    KeAcquireSpinLockAtDpcLevel(&Endpoint->StateChangeSpinLock);

    if (Endpoint->StateLast != Endpoint->StateNext)
    {
        State = USBPORT_ENDPOINT_UNKNOWN;
    }
    else
    {
        State = Endpoint->StateLast;
    }

    KeReleaseSpinLockFromDpcLevel(&Endpoint->StateChangeSpinLock);

    if (State != USBPORT_ENDPOINT_ACTIVE)
    {
        DPRINT("USBPORT_GetEndpointState: Endpoint - %p, State - %x\n",
               Endpoint,
               State);
    }

    return State;
}

VOID
NTAPI
USBPORT_SetEndpointState(IN PUSBPORT_ENDPOINT Endpoint,
                         IN ULONG State)
{
    PDEVICE_OBJECT FdoDevice;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;
    KIRQL OldIrql;

    DPRINT("USBPORT_SetEndpointState: Endpoint - %p, State - %x\n",
           Endpoint,
           State);

    FdoDevice = Endpoint->FdoDevice;
    FdoExtension = FdoDevice->DeviceExtension;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    KeAcquireSpinLock(&Endpoint->StateChangeSpinLock,
                      &Endpoint->EndpointStateOldIrql);

    if (!(Endpoint->Flags & ENDPOINT_FLAG_ROOTHUB_EP0))
    {
        if (Endpoint->Flags & ENDPOINT_FLAG_NUKE)
        {
            Endpoint->StateLast = State;
            Endpoint->StateNext = State;

            KeReleaseSpinLock(&Endpoint->StateChangeSpinLock,
                              Endpoint->EndpointStateOldIrql);

            USBPORT_InvalidateEndpointHandler(FdoDevice,
                                              Endpoint,
                                              INVALIDATE_ENDPOINT_WORKER_THREAD);
            return;
        }

        KeReleaseSpinLock(&Endpoint->StateChangeSpinLock,
                          Endpoint->EndpointStateOldIrql);

        KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &OldIrql);
        Packet->SetEndpointState(FdoExtension->MiniPortExt,
                                 Endpoint + 1,
                                 State);
        KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, OldIrql);

        Endpoint->StateNext = State;

        KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &OldIrql);
        Endpoint->FrameNumber = Packet->Get32BitFrameNumber(FdoExtension->MiniPortExt);
        KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, OldIrql);

        ExInterlockedInsertTailList(&FdoExtension->EpStateChangeList,
                                    &Endpoint->StateChangeLink,
                                    &FdoExtension->EpStateChangeSpinLock);

        KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &OldIrql);
        Packet->InterruptNextSOF(FdoExtension->MiniPortExt);
        KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, OldIrql);
    }
    else
    {
        Endpoint->StateLast = State;
        Endpoint->StateNext = State;

        if (State == USBPORT_ENDPOINT_REMOVE)
        {
            KeReleaseSpinLock(&Endpoint->StateChangeSpinLock,
                              Endpoint->EndpointStateOldIrql);

            USBPORT_InvalidateEndpointHandler(FdoDevice,
                                              Endpoint,
                                              INVALIDATE_ENDPOINT_WORKER_THREAD);
            return;
        }

        KeReleaseSpinLock(&Endpoint->StateChangeSpinLock,
                          Endpoint->EndpointStateOldIrql);
    }
}

VOID
NTAPI
USBPORT_AddPipeHandle(IN PUSBPORT_DEVICE_HANDLE DeviceHandle,
                      IN PUSBPORT_PIPE_HANDLE PipeHandle)
{
    DPRINT("USBPORT_AddPipeHandle: DeviceHandle - %p, PipeHandle - %p\n",
           DeviceHandle,
           PipeHandle);

    InsertTailList(&DeviceHandle->PipeHandleList, &PipeHandle->PipeLink);
}

VOID
NTAPI
USBPORT_RemovePipeHandle(IN PUSBPORT_DEVICE_HANDLE DeviceHandle,
                         IN PUSBPORT_PIPE_HANDLE PipeHandle)
{
    DPRINT("USBPORT_RemovePipeHandle: PipeHandle - %p\n", PipeHandle);

    RemoveEntryList(&PipeHandle->PipeLink);

    PipeHandle->PipeLink.Flink = NULL;
    PipeHandle->PipeLink.Blink = NULL;
}

BOOLEAN
NTAPI
USBPORT_ValidatePipeHandle(IN PUSBPORT_DEVICE_HANDLE DeviceHandle,
                           IN PUSBPORT_PIPE_HANDLE PipeHandle)
{
    PLIST_ENTRY HandleList;
    PUSBPORT_PIPE_HANDLE CurrentHandle;

    //DPRINT("USBPORT_ValidatePipeHandle: DeviceHandle - %p, PipeHandle - %p\n",
    //       DeviceHandle,
    //       PipeHandle);

    HandleList = DeviceHandle->PipeHandleList.Flink;

    while (HandleList != &DeviceHandle->PipeHandleList)
    {
        CurrentHandle = CONTAINING_RECORD(HandleList,
                                          USBPORT_PIPE_HANDLE,
                                          PipeLink);
  
        HandleList = HandleList->Flink;
  
        if (CurrentHandle == PipeHandle)
            return TRUE;
    }

    return FALSE;
}

BOOLEAN
NTAPI
USBPORT_DeleteEndpoint(IN PDEVICE_OBJECT FdoDevice,
                       IN PUSBPORT_ENDPOINT Endpoint)
{
    PUSBPORT_DEVICE_EXTENSION  FdoExtension;
    BOOLEAN Result;
    KIRQL OldIrql;

    DPRINT1("USBPORT_DeleteEndpoint: Endpoint - %p\n", Endpoint);

    FdoExtension = FdoDevice->DeviceExtension;

    if ((Endpoint->WorkerLink.Flink && Endpoint->WorkerLink.Blink) ||
        Endpoint->LockCounter != -1)
    {
        KeAcquireSpinLock(&FdoExtension->EndpointListSpinLock, &OldIrql);

        ExInterlockedInsertTailList(&FdoExtension->EndpointClosedList,
                                    &Endpoint->CloseLink,
                                    &FdoExtension->EndpointClosedSpinLock);

        KeReleaseSpinLock(&FdoExtension->EndpointListSpinLock, OldIrql);

        Result = FALSE;
    }
    else
    {
        KeAcquireSpinLock(&FdoExtension->EndpointListSpinLock, &OldIrql);

        RemoveEntryList(&Endpoint->EndpointLink);
        Endpoint->EndpointLink.Flink = NULL;
        Endpoint->EndpointLink.Blink = NULL;

        KeReleaseSpinLock(&FdoExtension->EndpointListSpinLock, OldIrql);

        MiniportCloseEndpoint(FdoDevice, Endpoint);

        if (Endpoint->HeaderBuffer)
        {
            USBPORT_FreeCommonBuffer(FdoDevice, Endpoint->HeaderBuffer);
        }

        ExFreePoolWithTag(Endpoint, USB_PORT_TAG);

        Result = TRUE;
    }

    return Result;
}

VOID
NTAPI
MiniportCloseEndpoint(IN PDEVICE_OBJECT FdoDevice,
                      IN PUSBPORT_ENDPOINT Endpoint)
{
    PUSBPORT_DEVICE_EXTENSION  FdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;
    BOOLEAN IsDoDisablePeriodic;
    ULONG TransferType;
    KIRQL OldIrql;

    DPRINT("MiniportCloseEndpoint: Endpoint - %p\n", Endpoint);

    FdoExtension = FdoDevice->DeviceExtension;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &OldIrql);

    if (Endpoint->Flags & ENDPOINT_FLAG_OPENED)
    {
        TransferType = Endpoint->EndpointProperties.TransferType;

        if (TransferType == USBPORT_TRANSFER_TYPE_INTERRUPT ||
            TransferType == USBPORT_TRANSFER_TYPE_ISOCHRONOUS)
        {
            --FdoExtension->PeriodicEndpoints;
        }

        IsDoDisablePeriodic = FdoExtension->PeriodicEndpoints == 0;

        Packet->CloseEndpoint(FdoExtension->MiniPortExt,
                              Endpoint + 1,
                              IsDoDisablePeriodic);

        Endpoint->Flags &= ~ENDPOINT_FLAG_OPENED;
        Endpoint->Flags |= ENDPOINT_FLAG_CLOSED;
    }

    KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, OldIrql);
}

VOID
NTAPI
USBPORT_ClosePipe(IN PUSBPORT_DEVICE_HANDLE DeviceHandle,
                  IN PDEVICE_OBJECT FdoDevice,
                  IN PUSBPORT_PIPE_HANDLE PipeHandle)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;
    PUSBPORT_ENDPOINT Endpoint;
    PUSBPORT_REGISTRATION_PACKET Packet;
    PUSB2_TT_EXTENSION TtExtension;
    ULONG ix;
    BOOLEAN IsReady;
    KIRQL OldIrql;

    DPRINT1("USBPORT_ClosePipe \n");

    FdoExtension = FdoDevice->DeviceExtension;

    if (PipeHandle->Flags & PIPE_HANDLE_FLAG_CLOSED)
        return;

    USBPORT_RemovePipeHandle(DeviceHandle, PipeHandle);

    PipeHandle->Flags |= PIPE_HANDLE_FLAG_CLOSED;

    if (PipeHandle->Flags & PIPE_HANDLE_FLAG_NULL_PACKET_SIZE)
    {
        PipeHandle->Flags &= ~PIPE_HANDLE_FLAG_NULL_PACKET_SIZE;
        return;
    }

    Endpoint = PipeHandle->Endpoint;

    KeAcquireSpinLock(&FdoExtension->EndpointListSpinLock, &OldIrql);

    if ((Endpoint->Flags & ENDPOINT_FLAG_ROOTHUB_EP0) &&
        (Endpoint->EndpointProperties.TransferType == USBPORT_TRANSFER_TYPE_INTERRUPT))
    {
        PdoExtension = FdoExtension->RootHubPdo->DeviceExtension;
        PdoExtension->Endpoint = NULL;
    }

    KeReleaseSpinLock(&FdoExtension->EndpointListSpinLock, OldIrql);

    while (TRUE)
    {
        IsReady = TRUE;

        KeAcquireSpinLock(&Endpoint->EndpointSpinLock,
                          &Endpoint->EndpointOldIrql);

        if (!IsListEmpty(&Endpoint->PendingTransferList))
            IsReady = FALSE;

        if (!IsListEmpty(&Endpoint->TransferList))
            IsReady = FALSE;

        if (!IsListEmpty(&Endpoint->CancelList))
            IsReady = FALSE;

        if (!IsListEmpty(&Endpoint->AbortList))
            IsReady = FALSE;

        KeAcquireSpinLockAtDpcLevel(&Endpoint->StateChangeSpinLock);
        if (Endpoint->StateLast != Endpoint->StateNext)
            IsReady = FALSE;
        KeReleaseSpinLockFromDpcLevel(&Endpoint->StateChangeSpinLock);

        KeReleaseSpinLock(&Endpoint->EndpointSpinLock,
                          Endpoint->EndpointOldIrql);

        if (InterlockedIncrement(&Endpoint->LockCounter))
            IsReady = FALSE;
        InterlockedDecrement(&Endpoint->LockCounter);

        if (IsReady == TRUE)
            break;

        USBPORT_Wait(FdoDevice, 1);
    }

    Endpoint->DeviceHandle = NULL;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    if (Packet->MiniPortFlags & USB_MINIPORT_FLAGS_USB2)
    {
        USBPORT_FreeBandwidthUSB2(FdoDevice, Endpoint);

        KeAcquireSpinLock(&FdoExtension->TtSpinLock, &OldIrql);

        TtExtension = Endpoint->TtExtension;
        DPRINT1("USBPORT_ClosePipe: TtExtension - %p\n", TtExtension);

        if (TtExtension)
        {
            RemoveEntryList(&Endpoint->TtLink);

            Endpoint->TtLink.Flink = NULL;
            Endpoint->TtLink.Blink = NULL;

            if (TtExtension->Flags & USB2_TT_EXTENSION_FLAG_DELETED)
            {
                if (IsListEmpty(&TtExtension->EndpointList))
                {
                    USBPORT_UpdateAllocatedBwTt(TtExtension);

                    for (ix = 0; ix < USB2_FRAMES; ix++)
                    {
                        FdoExtension->Bandwidth[ix] += TtExtension->MaxBandwidth;
                    }

                    DPRINT1("USBPORT_ClosePipe: ExFreePoolWithTag TtExtension - %p\n", TtExtension);
                    ExFreePoolWithTag(TtExtension, USB_PORT_TAG);
                }
            }
        }

        KeReleaseSpinLock(&FdoExtension->TtSpinLock, OldIrql);
    }
    else
    {
        USBPORT_FreeBandwidth(FdoDevice, Endpoint);
    }

    KeAcquireSpinLock(&Endpoint->EndpointSpinLock, &Endpoint->EndpointOldIrql);
    USBPORT_SetEndpointState(Endpoint, USBPORT_ENDPOINT_REMOVE);
    KeReleaseSpinLock(&Endpoint->EndpointSpinLock, Endpoint->EndpointOldIrql);

    USBPORT_SignalWorkerThread(FdoDevice);
}

MPSTATUS
NTAPI
MiniportOpenEndpoint(IN PDEVICE_OBJECT FdoDevice,
                     IN PUSBPORT_ENDPOINT Endpoint)
{
    PUSBPORT_DEVICE_EXTENSION  FdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;
    KIRQL OldIrql;
    ULONG TransferType;
    MPSTATUS MpStatus;

    DPRINT("MiniportOpenEndpoint: Endpoint - %p\n", Endpoint);

    FdoExtension = FdoDevice->DeviceExtension;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &OldIrql);

    Endpoint->Flags &= ~ENDPOINT_FLAG_CLOSED;

    MpStatus = Packet->OpenEndpoint(FdoExtension->MiniPortExt,
                                    &Endpoint->EndpointProperties,
                                    Endpoint + 1);

    if (!MpStatus)
    {
        TransferType = Endpoint->EndpointProperties.TransferType;

        if (TransferType == USBPORT_TRANSFER_TYPE_INTERRUPT ||
            TransferType == USBPORT_TRANSFER_TYPE_ISOCHRONOUS)
        {
            ++FdoExtension->PeriodicEndpoints;
        }

        Endpoint->Flags |= ENDPOINT_FLAG_OPENED;
    }

    KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, OldIrql);
    return MpStatus;
}

NTSTATUS
NTAPI
USBPORT_OpenPipe(IN PDEVICE_OBJECT FdoDevice,
                 IN PUSBPORT_DEVICE_HANDLE DeviceHandle,
                 IN PUSBPORT_PIPE_HANDLE PipeHandle,
                 IN OUT PUSBD_STATUS UsbdStatus)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_RHDEVICE_EXTENSION PdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;
    ULONG EndpointSize;
    PUSBPORT_ENDPOINT Endpoint;
    PUSBPORT_ENDPOINT_PROPERTIES EndpointProperties;
    PUSB_ENDPOINT_DESCRIPTOR EndpointDescriptor;
    UCHAR Direction;
    UCHAR Interval;
    UCHAR Period;
    USBPORT_ENDPOINT_REQUIREMENTS EndpointRequirements = {0};
    PUSBPORT_COMMON_BUFFER_HEADER HeaderBuffer;
    MPSTATUS MpStatus;
    USBD_STATUS USBDStatus;
    NTSTATUS Status;
    KIRQL OldIrql;
    USHORT MaxPacketSize;
    USHORT AdditionalTransaction;
    BOOLEAN IsAllocatedBandwidth;

    DPRINT1("USBPORT_OpenPipe: DeviceHandle - %p, FdoDevice - %p, PipeHandle - %p\n",
           DeviceHandle,
           FdoDevice,
           PipeHandle);

    FdoExtension = FdoDevice->DeviceExtension;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    EndpointSize = sizeof(USBPORT_ENDPOINT) + Packet->MiniPortEndpointSize;

    if (Packet->MiniPortFlags & USB_MINIPORT_FLAGS_USB2)
    {
        EndpointSize += sizeof(USB2_TT_ENDPOINT);
    }

    if (PipeHandle->EndpointDescriptor.wMaxPacketSize == 0)
    {
        USBPORT_AddPipeHandle(DeviceHandle, PipeHandle);

        PipeHandle->Flags = (PipeHandle->Flags & ~PIPE_HANDLE_FLAG_CLOSED) |
                             PIPE_HANDLE_FLAG_NULL_PACKET_SIZE;

        PipeHandle->Endpoint = (PUSBPORT_ENDPOINT)-1;

        return STATUS_SUCCESS;
    }

    Endpoint = ExAllocatePoolWithTag(NonPagedPool, EndpointSize, USB_PORT_TAG);

    if (!Endpoint)
    {
        DPRINT1("USBPORT_OpenPipe: Not allocated Endpoint!\n");
        Status = STATUS_INSUFFICIENT_RESOURCES;
        return Status;
    }

    RtlZeroMemory(Endpoint, EndpointSize);

    Endpoint->FdoDevice = FdoDevice;
    Endpoint->DeviceHandle = DeviceHandle;
    Endpoint->LockCounter = -1;

    Endpoint->TtExtension = DeviceHandle->TtExtension;

    if (DeviceHandle->TtExtension)
    {
        ExInterlockedInsertTailList(&DeviceHandle->TtExtension->EndpointList,
                                    &Endpoint->TtLink,
                                    &FdoExtension->TtSpinLock);
    }

    if (Packet->MiniPortFlags & USB_MINIPORT_FLAGS_USB2)
    {
        Endpoint->TtEndpoint = (PUSB2_TT_ENDPOINT)((ULONG_PTR)Endpoint +
                                                   sizeof(USBPORT_ENDPOINT) +
                                                   Packet->MiniPortEndpointSize);
    }
    else
    {
        Endpoint->TtEndpoint = NULL;
    }

    KeInitializeSpinLock(&Endpoint->EndpointSpinLock);
    KeInitializeSpinLock(&Endpoint->StateChangeSpinLock);

    InitializeListHead(&Endpoint->PendingTransferList);
    InitializeListHead(&Endpoint->TransferList);
    InitializeListHead(&Endpoint->CancelList);
    InitializeListHead(&Endpoint->AbortList);

    EndpointProperties = &Endpoint->EndpointProperties;
    EndpointDescriptor = &PipeHandle->EndpointDescriptor;

    MaxPacketSize = EndpointDescriptor->wMaxPacketSize & 0x7FF;
    AdditionalTransaction = (EndpointDescriptor->wMaxPacketSize >> 11) & 3;

    EndpointProperties->DeviceAddress = DeviceHandle->DeviceAddress;
    EndpointProperties->DeviceSpeed = DeviceHandle->DeviceSpeed;
    EndpointProperties->Period = 0;
    EndpointProperties->EndpointAddress = EndpointDescriptor->bEndpointAddress;
    EndpointProperties->TransactionPerMicroframe = AdditionalTransaction + 1;
    EndpointProperties->MaxPacketSize = MaxPacketSize;
    EndpointProperties->TotalMaxPacketSize = MaxPacketSize *
                                             (AdditionalTransaction + 1);

    if (Endpoint->TtExtension)
    {
        EndpointProperties->HubAddr = Endpoint->TtExtension->DeviceAddress;
    }
    else
    {
        EndpointProperties->HubAddr = -1;
    }

    EndpointProperties->PortNumber = DeviceHandle->PortNumber;

    switch (EndpointDescriptor->bmAttributes & USB_ENDPOINT_TYPE_MASK)
    {
        case USB_ENDPOINT_TYPE_CONTROL:
            EndpointProperties->TransferType = USBPORT_TRANSFER_TYPE_CONTROL;

            if (EndpointProperties->EndpointAddress == 0)
            {
                EndpointProperties->MaxTransferSize = 0x1000; // OUT Ep0
            }
            else
            {
                EndpointProperties->MaxTransferSize = 0x10000;
            }

            break;

        case USB_ENDPOINT_TYPE_ISOCHRONOUS:
            DPRINT1("USBPORT_OpenPipe: USB_ENDPOINT_TYPE_ISOCHRONOUS UNIMPLEMENTED. FIXME. \n");
            EndpointProperties->TransferType = USBPORT_TRANSFER_TYPE_ISOCHRONOUS;
            EndpointProperties->MaxTransferSize = 0x1000000;
            break;

        case USB_ENDPOINT_TYPE_BULK:
            EndpointProperties->TransferType = USBPORT_TRANSFER_TYPE_BULK;
            EndpointProperties->MaxTransferSize = 0x10000;
            break;

        case USB_ENDPOINT_TYPE_INTERRUPT:
            EndpointProperties->TransferType = USBPORT_TRANSFER_TYPE_INTERRUPT;
            EndpointProperties->MaxTransferSize = 0x400;
            break;
    }

    if (EndpointProperties->TransferType == USBPORT_TRANSFER_TYPE_INTERRUPT)
    {
        if (EndpointProperties->DeviceSpeed == UsbHighSpeed)
        {
            Interval = USBPORT_NormalizeHsInterval(EndpointDescriptor->bInterval);
        }
        else
        {
            Interval = EndpointDescriptor->bInterval;
        }

        EndpointProperties->Period = ENDPOINT_INTERRUPT_32ms;

        if (Interval && (Interval < USB2_FRAMES))
        {
            if ((EndpointProperties->DeviceSpeed != UsbLowSpeed) ||
                (Interval >= ENDPOINT_INTERRUPT_8ms))
            {
                if (!(Interval & ENDPOINT_INTERRUPT_32ms))
                {
                    Period = EndpointProperties->Period;

                    do
                    {
                        Period >>= 1;
                    }
                    while (!(Period & Interval));

                    EndpointProperties->Period = Period;
                }
            }
            else
            {
                EndpointProperties->Period = ENDPOINT_INTERRUPT_8ms;
            }
        }
    }

    if (EndpointProperties->TransferType == USB_ENDPOINT_TYPE_ISOCHRONOUS)
    {
        if (EndpointProperties->DeviceSpeed == UsbHighSpeed)
        {
            EndpointProperties->Period =
                USBPORT_NormalizeHsInterval(EndpointDescriptor->bInterval);
        }
        else
        {
            EndpointProperties->Period = ENDPOINT_INTERRUPT_1ms;
        }
    }

    if ((DeviceHandle->Flags & DEVICE_HANDLE_FLAG_ROOTHUB) != 0)
    {
        Endpoint->Flags |= ENDPOINT_FLAG_ROOTHUB_EP0;
    }

    if (Packet->MiniPortFlags & USB_MINIPORT_FLAGS_USB2)
    {
        IsAllocatedBandwidth = USBPORT_AllocateBandwidthUSB2(FdoDevice, Endpoint);
    }
    else
    {
        EndpointProperties->UsbBandwidth = USBPORT_CalculateUsbBandwidth(FdoDevice,
                                                                         Endpoint);

        IsAllocatedBandwidth = USBPORT_AllocateBandwidth(FdoDevice, Endpoint);
    }

    if (!IsAllocatedBandwidth)
    {
        Status = USBPORT_USBDStatusToNtStatus(NULL, USBD_STATUS_NO_BANDWIDTH);

        if (UsbdStatus)
        {
            *UsbdStatus = USBD_STATUS_NO_BANDWIDTH;
        }

        goto ExitWithError;
    }

    Direction = USB_ENDPOINT_DIRECTION_OUT(EndpointDescriptor->bEndpointAddress);
    EndpointProperties->Direction = Direction;

    if (DeviceHandle->IsRootHub)
    {
        Endpoint->EndpointWorker = 0; // USBPORT_RootHubEndpointWorker;

        Endpoint->Flags |= ENDPOINT_FLAG_ROOTHUB_EP0;

        Endpoint->StateLast = USBPORT_ENDPOINT_ACTIVE;
        Endpoint->StateNext = USBPORT_ENDPOINT_ACTIVE;

        PdoExtension = FdoExtension->RootHubPdo->DeviceExtension;

        if (EndpointProperties->TransferType == USBPORT_TRANSFER_TYPE_INTERRUPT)
        {
            PdoExtension->Endpoint = Endpoint;
        }

        USBDStatus = USBD_STATUS_SUCCESS;
    }
    else
    {
        Endpoint->EndpointWorker = 1; // USBPORT_DmaEndpointWorker;

        KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &OldIrql);

        Packet->QueryEndpointRequirements(FdoExtension->MiniPortExt,
                                          &Endpoint->EndpointProperties,
                                          &EndpointRequirements);

        KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, OldIrql);

        if ((EndpointProperties->TransferType == USBPORT_TRANSFER_TYPE_BULK) ||
            (EndpointProperties->TransferType == USBPORT_TRANSFER_TYPE_INTERRUPT))
        {
            EndpointProperties->MaxTransferSize = EndpointRequirements.MaxTransferSize;
        }

        if (EndpointRequirements.HeaderBufferSize)
        {
            HeaderBuffer = USBPORT_AllocateCommonBuffer(FdoDevice,
                                                        EndpointRequirements.HeaderBufferSize);
        }
        else
        {
            HeaderBuffer = NULL;
        }

        if (HeaderBuffer || (EndpointRequirements.HeaderBufferSize == 0))
        {
            Endpoint->HeaderBuffer = HeaderBuffer;

            if (HeaderBuffer)
            {
                EndpointProperties->BufferVA = HeaderBuffer->VirtualAddress;
                EndpointProperties->BufferPA = HeaderBuffer->PhysicalAddress;
                EndpointProperties->BufferLength = HeaderBuffer->BufferLength; // BufferLength + LengthPadded;
            }

            MpStatus = MiniportOpenEndpoint(FdoDevice, Endpoint);

            Endpoint->Flags |= ENDPOINT_FLAG_DMA_TYPE;
            Endpoint->Flags |= ENDPOINT_FLAG_QUEUENE_EMPTY;

            if (MpStatus == 0)
            {
                ULONG State;

                KeAcquireSpinLock(&Endpoint->EndpointSpinLock,
                                  &Endpoint->EndpointOldIrql);

                Endpoint->StateLast = USBPORT_ENDPOINT_PAUSED;
                Endpoint->StateNext = USBPORT_ENDPOINT_PAUSED;

                USBPORT_SetEndpointState(Endpoint, USBPORT_ENDPOINT_ACTIVE);

                KeReleaseSpinLock(&Endpoint->EndpointSpinLock,
                                  Endpoint->EndpointOldIrql);

                while (TRUE)
                {
                    KeAcquireSpinLock(&Endpoint->EndpointSpinLock,
                                      &Endpoint->EndpointOldIrql);

                    State = USBPORT_GetEndpointState(Endpoint);

                    KeReleaseSpinLock(&Endpoint->EndpointSpinLock,
                                      Endpoint->EndpointOldIrql);

                    if (State == USBPORT_ENDPOINT_ACTIVE)
                    {
                        break;
                    }

                    USBPORT_Wait(FdoDevice, 1); // 1 msec.
                }
            }
        }
        else
        {
            MpStatus = MP_STATUS_NO_RESOURCES;
            Endpoint->HeaderBuffer = NULL;
        }

        if (MpStatus)
        {
            USBDStatus = USBD_STATUS_INSUFFICIENT_RESOURCES;
        }
        else
        {
            USBDStatus = USBD_STATUS_SUCCESS;
        }
    }

    if (UsbdStatus)
    {
        *UsbdStatus = USBDStatus;
    }

    Status = USBPORT_USBDStatusToNtStatus(NULL, USBDStatus);

    if (NT_SUCCESS(Status))
    {
        USBPORT_AddPipeHandle(DeviceHandle, PipeHandle);

        ExInterlockedInsertTailList(&FdoExtension->EndpointList,
                                    &Endpoint->EndpointLink,
                                    &FdoExtension->EndpointListSpinLock);

        PipeHandle->Endpoint = Endpoint;
        PipeHandle->Flags &= ~PIPE_HANDLE_FLAG_CLOSED;

        return Status;
    }

ExitWithError:

    if (Endpoint)
    {
        if (IsAllocatedBandwidth)
        {
            if (Packet->MiniPortFlags & USB_MINIPORT_FLAGS_USB2)
            {
                USBPORT_FreeBandwidthUSB2(FdoDevice, Endpoint);
            }
            else
            {
                USBPORT_FreeBandwidth(FdoDevice, Endpoint);
            }
        }

        if (Endpoint->TtExtension)
        {
            KeAcquireSpinLock(&FdoExtension->TtSpinLock, &OldIrql);
            RemoveEntryList(&Endpoint->TtLink);
            KeReleaseSpinLock(&FdoExtension->TtSpinLock, OldIrql);
        }

        ExFreePoolWithTag(Endpoint, USB_PORT_TAG);
    }

    DPRINT1("USBPORT_OpenPipe: Status - %lx\n", Status);
    return Status;
}

NTSTATUS
NTAPI
USBPORT_ReopenPipe(IN PDEVICE_OBJECT FdoDevice,
                   IN PUSBPORT_ENDPOINT Endpoint)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_COMMON_BUFFER_HEADER HeaderBuffer;
    USBPORT_ENDPOINT_REQUIREMENTS EndpointRequirements = {0};
    PUSBPORT_REGISTRATION_PACKET Packet;
    KIRQL MiniportOldIrql;
    NTSTATUS Status;

    DPRINT1("USBPORT_ReopenPipe ... \n");

    FdoExtension = FdoDevice->DeviceExtension;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    while (TRUE)
    {
        if (!InterlockedIncrement(&Endpoint->LockCounter))
            break;

        InterlockedDecrement(&Endpoint->LockCounter);
        USBPORT_Wait(FdoDevice, 1);
    }

    KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &MiniportOldIrql);

    Packet->SetEndpointState(FdoExtension->MiniPortExt,
                             Endpoint + 1,
                             USBPORT_ENDPOINT_REMOVE);

    KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, MiniportOldIrql);

    USBPORT_Wait(FdoDevice, 2);

    MiniportCloseEndpoint(FdoDevice, Endpoint);

    RtlZeroMemory(Endpoint + 1,
                  Packet->MiniPortEndpointSize);

    if (Endpoint->HeaderBuffer)
    {
        USBPORT_FreeCommonBuffer(FdoDevice, Endpoint->HeaderBuffer);
        Endpoint->HeaderBuffer = NULL;
    }

    KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &MiniportOldIrql);

    Packet->QueryEndpointRequirements(FdoExtension->MiniPortExt,
                                      &Endpoint->EndpointProperties,
                                      &EndpointRequirements);

    KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, MiniportOldIrql);

    if (EndpointRequirements.HeaderBufferSize)
    {
        HeaderBuffer = USBPORT_AllocateCommonBuffer(FdoDevice,
                                                    EndpointRequirements.HeaderBufferSize);
    }
    else
    {
        HeaderBuffer = NULL;
    }

    if (HeaderBuffer || EndpointRequirements.HeaderBufferSize == 0)
    {
        Endpoint->HeaderBuffer = HeaderBuffer;
        Status = STATUS_SUCCESS;
    }
    else
    {
        Endpoint->HeaderBuffer = 0;
        Status = STATUS_INSUFFICIENT_RESOURCES;
    }

    if (Endpoint->HeaderBuffer && HeaderBuffer)
    {
        Endpoint->EndpointProperties.BufferVA = HeaderBuffer->VirtualAddress;
        Endpoint->EndpointProperties.BufferPA = HeaderBuffer->PhysicalAddress;
        Endpoint->EndpointProperties.BufferLength = HeaderBuffer->BufferLength;
    }

    if (NT_SUCCESS(Status))
    {
        MiniportOpenEndpoint(FdoDevice, Endpoint);

        KeAcquireSpinLock(&Endpoint->EndpointSpinLock, &Endpoint->EndpointOldIrql);
        KeAcquireSpinLockAtDpcLevel(&Endpoint->StateChangeSpinLock);

        if (Endpoint->StateLast == USBPORT_ENDPOINT_ACTIVE)
        {
            KeAcquireSpinLockAtDpcLevel(&FdoExtension->MiniportSpinLock);

            Packet->SetEndpointState(FdoExtension->MiniPortExt,
                                     Endpoint + 1,
                                     USBPORT_ENDPOINT_ACTIVE);

            KeReleaseSpinLockFromDpcLevel(&FdoExtension->MiniportSpinLock);
        }

        KeReleaseSpinLockFromDpcLevel(&Endpoint->StateChangeSpinLock);
        KeReleaseSpinLock(&Endpoint->EndpointSpinLock, Endpoint->EndpointOldIrql);
    }

    InterlockedDecrement(&Endpoint->LockCounter);

    return Status;
}

VOID
NTAPI
USBPORT_FlushClosedEndpointList(IN PDEVICE_OBJECT FdoDevice)
{
    PUSBPORT_DEVICE_EXTENSION  FdoExtension;
    KIRQL OldIrql;
    PLIST_ENTRY ClosedList;
    PUSBPORT_ENDPOINT Endpoint;

    DPRINT_CORE("USBPORT_FlushClosedEndpointList: ... \n");

    FdoExtension = FdoDevice->DeviceExtension;

    KeAcquireSpinLock(&FdoExtension->EndpointClosedSpinLock, &OldIrql);
    ClosedList = &FdoExtension->EndpointClosedList;

    while (!IsListEmpty(ClosedList))
    {
        Endpoint = CONTAINING_RECORD(ClosedList->Flink,
                                     USBPORT_ENDPOINT,
                                     CloseLink);

        RemoveHeadList(ClosedList);
        Endpoint->CloseLink.Flink = NULL;
        Endpoint->CloseLink.Blink = NULL;

        KeReleaseSpinLock(&FdoExtension->EndpointClosedSpinLock, OldIrql);

        USBPORT_DeleteEndpoint(FdoDevice, Endpoint);

        KeAcquireSpinLock(&FdoExtension->EndpointClosedSpinLock, &OldIrql);
    }

    KeReleaseSpinLock(&FdoExtension->EndpointClosedSpinLock, OldIrql);
}

VOID
NTAPI
USBPORT_InvalidateEndpointHandler(IN PDEVICE_OBJECT FdoDevice,
                                  IN PUSBPORT_ENDPOINT Endpoint,
                                  IN ULONG Type)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;
    PLIST_ENTRY Entry;
    PLIST_ENTRY WorkerLink;
    PUSBPORT_ENDPOINT endpoint;
    KIRQL OldIrql;
    BOOLEAN IsAddEntry = FALSE;

    DPRINT_CORE("USBPORT_InvalidateEndpointHandler: Endpoint - %p, Type - %x\n",
                Endpoint,
                Type);

    FdoExtension = FdoDevice->DeviceExtension;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    if (Endpoint)
    {
        WorkerLink = &Endpoint->WorkerLink;
        KeAcquireSpinLock(&FdoExtension->EndpointListSpinLock, &OldIrql);
        DPRINT_CORE("USBPORT_InvalidateEndpointHandler: KeAcquireSpinLock \n");

        if ((!WorkerLink->Flink || !WorkerLink->Blink) &&
            !(Endpoint->Flags & ENDPOINT_FLAG_IDLE) &&
            USBPORT_GetEndpointState(Endpoint) != USBPORT_ENDPOINT_CLOSED)
        {
            DPRINT_CORE("USBPORT_InvalidateEndpointHandler: InsertTailList \n");
            InsertTailList(&FdoExtension->WorkerList, WorkerLink);
            IsAddEntry = TRUE;
        }

        KeReleaseSpinLock(&FdoExtension->EndpointListSpinLock, OldIrql);

        if (Endpoint->Flags & ENDPOINT_FLAG_ROOTHUB_EP0)
            Type = INVALIDATE_ENDPOINT_WORKER_THREAD;
    }
    else
    {
        KeAcquireSpinLock(&FdoExtension->EndpointListSpinLock, &OldIrql);

        Entry = &FdoExtension->EndpointList;

        while (Entry && Entry != &FdoExtension->EndpointList)
        {
            endpoint = CONTAINING_RECORD(Entry,
                                         USBPORT_ENDPOINT,
                                         EndpointLink);

            if (!endpoint->WorkerLink.Flink || !endpoint->WorkerLink.Blink)
            {
                if (!(endpoint->Flags & ENDPOINT_FLAG_IDLE) &&
                    !(endpoint->Flags & ENDPOINT_FLAG_ROOTHUB_EP0) &&
                    USBPORT_GetEndpointState(endpoint) != USBPORT_ENDPOINT_CLOSED)
                {
                    DPRINT_CORE("USBPORT_InvalidateEndpointHandler: InsertTailList \n");
                    InsertTailList(&FdoExtension->WorkerList, &endpoint->WorkerLink);
                    IsAddEntry = TRUE;
                }
            }

            Entry = endpoint->EndpointLink.Flink;
        }

        KeReleaseSpinLock(&FdoExtension->EndpointListSpinLock, OldIrql);
    }

    if (FdoExtension->Flags & USBPORT_FLAG_HC_SUSPEND)
    {
        Type = INVALIDATE_ENDPOINT_WORKER_THREAD;
    }
    else if (IsAddEntry == FALSE && Type == INVALIDATE_ENDPOINT_INT_NEXT_SOF)
    {
        Type = INVALIDATE_ENDPOINT_ONLY;
    }

    switch (Type)
    {
        case INVALIDATE_ENDPOINT_WORKER_THREAD:
            USBPORT_SignalWorkerThread(FdoDevice);
            break;

        case INVALIDATE_ENDPOINT_WORKER_DPC:
            KeInsertQueueDpc(&FdoExtension->WorkerRequestDpc, NULL, NULL);
            break;

        case INVALIDATE_ENDPOINT_INT_NEXT_SOF:
            KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &OldIrql);
            Packet->InterruptNextSOF(FdoExtension->MiniPortExt);
            KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, OldIrql);
            break;
    }
}

ULONG
NTAPI
USBPORT_DmaEndpointPaused(IN PDEVICE_OBJECT FdoDevice,
                          IN PUSBPORT_ENDPOINT Endpoint)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;
    PLIST_ENTRY Entry;
    PUSBPORT_TRANSFER Transfer;
    PURB Urb;
    ULONG Frame;
    ULONG CurrentFrame;
    ULONG CompletedLen = 0;
    KIRQL OldIrql;

    DPRINT_CORE("USBPORT_DmaEndpointPaused \n");

    FdoExtension = FdoDevice->DeviceExtension;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    Entry = Endpoint->TransferList.Flink;

    if (Entry == &Endpoint->TransferList)
        return USBPORT_ENDPOINT_ACTIVE;

    while (Entry && Entry != &Endpoint->TransferList)
    {
        Transfer = CONTAINING_RECORD(Entry,
                                     USBPORT_TRANSFER,
                                     TransferLink);

        if (Transfer->Flags & (TRANSFER_FLAG_CANCELED | TRANSFER_FLAG_ABORTED))
        {
            if (Transfer->Flags & TRANSFER_FLAG_ISO &&
                Transfer->Flags & TRANSFER_FLAG_SUBMITED &&
                !(Endpoint->Flags & ENDPOINT_FLAG_NUKE))
            {
                Urb = Transfer->Urb;

                Frame = Urb->UrbIsochronousTransfer.StartFrame +
                        Urb->UrbIsochronousTransfer.NumberOfPackets;

                KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &OldIrql);
                CurrentFrame = Packet->Get32BitFrameNumber(FdoExtension->MiniPortExt);
                KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, OldIrql);

                if (Frame + 1 > CurrentFrame)
                {
                    return USBPORT_GetEndpointState(Endpoint);
                }
            }

            if ((Transfer->Flags & TRANSFER_FLAG_SUBMITED) &&
                 !(Endpoint->Flags & ENDPOINT_FLAG_NUKE))
            {
                KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &OldIrql);

                Packet->AbortTransfer(FdoExtension->MiniPortExt,
                                      Endpoint + 1,
                                      Transfer->MiniportTransfer,
                                      &CompletedLen);

                KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, OldIrql);

                if (Transfer->Flags & TRANSFER_FLAG_ISO)
                {
                    DPRINT1("USBPORT_DmaEndpointActive: FIXME call USBPORT_FlushIsoTransfer\n");
                    ASSERT(FALSE); //USBPORT_FlushIsoTransfer();
                }
                else
                {
                    Transfer->CompletedTransferLen = CompletedLen;
                }
            }

            RemoveEntryList(&Transfer->TransferLink);
            Entry = Transfer->TransferLink.Flink;

            if (Transfer->Flags & TRANSFER_FLAG_SPLITED)
            {
                USBPORT_CancelSplitTransfer(Transfer);
            }
            else
            {
                InsertTailList(&Endpoint->CancelList, &Transfer->TransferLink);
            }
        }
        else
        {
            Entry = Transfer->TransferLink.Flink;
        }
    }

    return USBPORT_ENDPOINT_ACTIVE;
}

ULONG
NTAPI
USBPORT_DmaEndpointActive(IN PDEVICE_OBJECT FdoDevice,
                          IN PUSBPORT_ENDPOINT Endpoint)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;
    PLIST_ENTRY Entry;
    PUSBPORT_TRANSFER Transfer;
    LARGE_INTEGER TimeOut;
    MPSTATUS MpStatus;
    KIRQL OldIrql;

    DPRINT_CORE("USBPORT_DmaEndpointActive \n");

    FdoExtension = FdoDevice->DeviceExtension;

    Entry = Endpoint->TransferList.Flink;

    while (Entry && Entry != &Endpoint->TransferList)
    {
        Transfer = CONTAINING_RECORD(Entry,
                                     USBPORT_TRANSFER,
                                     TransferLink);

        if (!(Transfer->Flags & TRANSFER_FLAG_SUBMITED) &&
             !(Endpoint->Flags & ENDPOINT_FLAG_NUKE))
        {
            KeAcquireSpinLock(&FdoExtension->MiniportSpinLock, &OldIrql);

            Packet = &FdoExtension->MiniPortInterface->Packet;

            if (Transfer->Flags & TRANSFER_FLAG_ISO)
            {
                DPRINT1("USBPORT_DmaEndpointActive: FIXME call SubmitIsoTransfer\n");

                MpStatus = Packet->SubmitIsoTransfer(FdoExtension->MiniPortExt,
                                                     Endpoint + 1,
                                                     &Transfer->TransferParameters,
                                                     Transfer->MiniportTransfer,
                                                     NULL);//&Transfer->IsoTransferParameters);
            }
            else
            {
                MpStatus = Packet->SubmitTransfer(FdoExtension->MiniPortExt,
                                                  Endpoint + 1,
                                                  &Transfer->TransferParameters,
                                                  Transfer->MiniportTransfer,
                                                  &Transfer->SgList);
            }

            KeReleaseSpinLock(&FdoExtension->MiniportSpinLock, OldIrql);

            if (MpStatus)
            {
                if ((MpStatus != MP_STATUS_FAILURE) && Transfer->Flags & TRANSFER_FLAG_ISO)
                {
                    DPRINT1("USBPORT_DmaEndpointActive: FIXME call USBPORT_ErrorCompleteIsoTransfer\n");
                    ASSERT(FALSE); //USBPORT_ErrorCompleteIsoTransfer();
                }

                return USBPORT_ENDPOINT_ACTIVE;
            }

            Transfer->Flags |= TRANSFER_FLAG_SUBMITED;
            KeQuerySystemTime(&Transfer->Time);

            TimeOut.QuadPart = 10000 * Transfer->TimeOut;
            Transfer->Time.QuadPart += TimeOut.QuadPart;
        }

        if (Transfer->Flags & (TRANSFER_FLAG_CANCELED | TRANSFER_FLAG_ABORTED))
        {
            return USBPORT_ENDPOINT_PAUSED;
        }

        Entry = Transfer->TransferLink.Flink;
    }

    return USBPORT_ENDPOINT_ACTIVE;
}

VOID
NTAPI
USBPORT_DmaEndpointWorker(IN PUSBPORT_ENDPOINT Endpoint)
{
    PDEVICE_OBJECT FdoDevice;
    ULONG PrevState;
    ULONG EndpointState;
    BOOLEAN IsPaused = FALSE;

    DPRINT_CORE("USBPORT_DmaEndpointWorker ... \n");

    FdoDevice = Endpoint->FdoDevice;

    KeAcquireSpinLock(&Endpoint->EndpointSpinLock, &Endpoint->EndpointOldIrql);

    PrevState = USBPORT_GetEndpointState(Endpoint);

    if (PrevState == USBPORT_ENDPOINT_PAUSED)
    {
        EndpointState = USBPORT_DmaEndpointPaused(FdoDevice, Endpoint);
    }
    else if (PrevState == USBPORT_ENDPOINT_ACTIVE)
    {
        EndpointState = USBPORT_DmaEndpointActive(FdoDevice, Endpoint);
    }
    else
    {
#ifndef NDEBUG_USBPORT_CORE
        DPRINT1("USBPORT_DmaEndpointWorker: DbgBreakPoint. EndpointState - %x\n",
                EndpointState);
        DbgBreakPoint();
#endif
        EndpointState = USBPORT_ENDPOINT_UNKNOWN;
    }

    KeReleaseSpinLock(&Endpoint->EndpointSpinLock, Endpoint->EndpointOldIrql);

    USBPORT_FlushCancelList(Endpoint);

    KeAcquireSpinLock(&Endpoint->EndpointSpinLock, &Endpoint->EndpointOldIrql);

    if (EndpointState == PrevState)
    {
        if (EndpointState == USBPORT_ENDPOINT_PAUSED)
        {
            IsPaused = TRUE;
        }
    }
    else
    {
        USBPORT_SetEndpointState(Endpoint, EndpointState);
    }

    KeReleaseSpinLock(&Endpoint->EndpointSpinLock, Endpoint->EndpointOldIrql);

    if (IsPaused)
    {
       USBPORT_InvalidateEndpointHandler(FdoDevice,
                                         Endpoint,
                                         INVALIDATE_ENDPOINT_WORKER_THREAD);
    }

    DPRINT_CORE("USBPORT_DmaEndpointWorker exit \n");
}

BOOLEAN
NTAPI
USBPORT_EndpointWorker(IN PUSBPORT_ENDPOINT Endpoint,
                       IN BOOLEAN LockNotChecked)
{
    PDEVICE_OBJECT FdoDevice;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;
    ULONG EndpointState;

    DPRINT_CORE("USBPORT_EndpointWorker: Endpoint - %p, LockNotChecked - %x\n",
           Endpoint,
           LockNotChecked);

    FdoDevice = Endpoint->FdoDevice;
    FdoExtension = FdoDevice->DeviceExtension;
    Packet = &FdoExtension->MiniPortInterface->Packet;

    if (LockNotChecked == FALSE)
    {
        if (InterlockedIncrement(&Endpoint->LockCounter))
        {
            InterlockedDecrement(&Endpoint->LockCounter);
            DPRINT_CORE("USBPORT_EndpointWorker: LockCounter > 0\n");
            return TRUE;
        }
    }

    ASSERT(KeGetCurrentIrql() == DISPATCH_LEVEL);

    KeAcquireSpinLockAtDpcLevel(&Endpoint->EndpointSpinLock);

    if (USBPORT_GetEndpointState(Endpoint) == USBPORT_ENDPOINT_CLOSED)
    {
        KeReleaseSpinLockFromDpcLevel(&Endpoint->EndpointSpinLock);
        InterlockedDecrement(&Endpoint->LockCounter);
        DPRINT_CORE("USBPORT_EndpointWorker: State == USBPORT_ENDPOINT_CLOSED. return FALSE\n");
        return FALSE;
    }

    if ((Endpoint->Flags & (ENDPOINT_FLAG_ROOTHUB_EP0 | ENDPOINT_FLAG_NUKE)) == 0)
    {
        KeAcquireSpinLockAtDpcLevel(&FdoExtension->MiniportSpinLock);
        Packet->PollEndpoint(FdoExtension->MiniPortExt, Endpoint + 1);
        KeReleaseSpinLockFromDpcLevel(&FdoExtension->MiniportSpinLock);
    }

    EndpointState = USBPORT_GetEndpointState(Endpoint);

    if (EndpointState == USBPORT_ENDPOINT_REMOVE)
    {
        KeAcquireSpinLockAtDpcLevel(&Endpoint->StateChangeSpinLock);
        Endpoint->StateLast = USBPORT_ENDPOINT_CLOSED;
        Endpoint->StateNext = USBPORT_ENDPOINT_CLOSED;
        KeReleaseSpinLockFromDpcLevel(&Endpoint->StateChangeSpinLock);

        KeReleaseSpinLockFromDpcLevel(&Endpoint->EndpointSpinLock);

        KeAcquireSpinLockAtDpcLevel(&FdoExtension->EndpointListSpinLock);

        ExInterlockedInsertTailList(&FdoExtension->EndpointClosedList,
                                    &Endpoint->CloseLink,
                                    &FdoExtension->EndpointClosedSpinLock);

        KeReleaseSpinLockFromDpcLevel(&FdoExtension->EndpointListSpinLock);

        InterlockedDecrement(&Endpoint->LockCounter);
        DPRINT_CORE("USBPORT_EndpointWorker: State == USBPORT_ENDPOINT_REMOVE. return FALSE\n");
        return FALSE;
    }

    if (!IsListEmpty(&Endpoint->PendingTransferList) ||
        !IsListEmpty(&Endpoint->TransferList) ||
        !IsListEmpty(&Endpoint->CancelList))
    {
        KeReleaseSpinLockFromDpcLevel(&Endpoint->EndpointSpinLock);

        EndpointState = USBPORT_GetEndpointState(Endpoint);

        KeAcquireSpinLockAtDpcLevel(&Endpoint->StateChangeSpinLock);
        if (EndpointState == Endpoint->StateNext)
        {
            KeReleaseSpinLockFromDpcLevel(&Endpoint->StateChangeSpinLock);

            if (Endpoint->EndpointWorker)
            {
                USBPORT_DmaEndpointWorker(Endpoint);
            }
            else
            {
                USBPORT_RootHubEndpointWorker(Endpoint);
            }

            USBPORT_FlushAbortList(Endpoint);

            InterlockedDecrement(&Endpoint->LockCounter);
            DPRINT_CORE("USBPORT_EndpointWorker: return FALSE\n");
            return FALSE;
        }

        KeReleaseSpinLockFromDpcLevel(&Endpoint->StateChangeSpinLock);
        InterlockedDecrement(&Endpoint->LockCounter);

        DPRINT_CORE("USBPORT_EndpointWorker: return TRUE\n");
        return TRUE;
    }

    KeReleaseSpinLockFromDpcLevel(&Endpoint->EndpointSpinLock);

    USBPORT_FlushAbortList(Endpoint);

    InterlockedDecrement(&Endpoint->LockCounter);
    DPRINT_CORE("USBPORT_EndpointWorker: return FALSE\n");
    return FALSE;
}