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

#include "usbport.h"

#define NDEBUG
#include <debug.h>

NTSTATUS
NTAPI
USBPORT_SendSetupPacket(IN PUSBPORT_DEVICE_HANDLE DeviceHandle,
                        IN PDEVICE_OBJECT FdoDevice,
                        IN PUSB_DEFAULT_PIPE_SETUP_PACKET SetupPacket,
                        IN PVOID Buffer,
                        IN ULONG Length,
                        IN OUT PULONG TransferedLen,
                        IN OUT PUSBD_STATUS pUSBDStatus)
{
    PURB Urb;
    PMDL Mdl;
    USBD_STATUS USBDStatus;
    KEVENT Event;
    NTSTATUS Status;

    DPRINT("USBPORT_SendSetupPacket: DeviceHandle - %p, FdoDevice - %p, SetupPacket - %p, Buffer - %p, Length - %x, TransferedLen - %x, pUSBDStatus - %x\n",
           DeviceHandle,
           FdoDevice,
           SetupPacket,
           Buffer,
           Length,
           TransferedLen,
           pUSBDStatus);

    KeInitializeEvent(&Event, NotificationEvent, FALSE);

    Urb = ExAllocatePoolWithTag(NonPagedPool,
                                sizeof(struct _URB_CONTROL_TRANSFER),
                                USB_PORT_TAG);

    if (Urb)
    {
        InterlockedIncrement(&DeviceHandle->DeviceHandleLock);

        RtlZeroMemory(Urb, sizeof(struct _URB_CONTROL_TRANSFER));

        RtlCopyMemory(Urb->UrbControlTransfer.SetupPacket,
                      SetupPacket,
                      sizeof(USB_DEFAULT_PIPE_SETUP_PACKET));

        Urb->UrbHeader.Length = sizeof(struct _URB_CONTROL_TRANSFER);
        Urb->UrbHeader.Function = URB_FUNCTION_CONTROL_TRANSFER;
        Urb->UrbHeader.UsbdDeviceHandle = DeviceHandle;
        Urb->UrbHeader.UsbdFlags = 0;

        Urb->UrbControlTransfer.PipeHandle = &DeviceHandle->PipeHandle;
        Urb->UrbControlTransfer.TransferBufferLength = Length;
        Urb->UrbControlTransfer.TransferBuffer = Buffer;
        Urb->UrbControlTransfer.TransferBufferMDL = NULL;

        Urb->UrbControlTransfer.TransferFlags = USBD_SHORT_TRANSFER_OK |
                                                USBD_TRANSFER_DIRECTION;

        if (SetupPacket->bmRequestType.Dir != BMREQUEST_DEVICE_TO_HOST)
        {
            Urb->UrbControlTransfer.TransferFlags &= ~USBD_TRANSFER_DIRECTION_IN;
        }

        Status = STATUS_SUCCESS;

        if (Length)
        {
            Mdl = IoAllocateMdl(Buffer, Length, FALSE, FALSE, NULL);

            Urb->UrbControlTransfer.TransferBufferMDL = Mdl;

            if (Mdl)
            {
                Urb->UrbHeader.UsbdFlags |= USBD_FLAG_ALLOCATED_MDL;
                MmBuildMdlForNonPagedPool(Mdl);
            }
            else
            {
                Status = USBPORT_USBDStatusToNtStatus(NULL,
                                                      USBD_STATUS_INSUFFICIENT_RESOURCES);
            }
        }

        if (NT_SUCCESS(Status))
        {
            USBDStatus = USBPORT_AllocateTransfer(FdoDevice,
                                                  Urb,
                                                  NULL,
                                                  NULL,
                                                  &Event);

            if (USBD_SUCCESS(USBDStatus))
            {
                InterlockedIncrement(&DeviceHandle->DeviceHandleLock);

                USBPORT_QueueTransferUrb(Urb);

                KeWaitForSingleObject(&Event,
                                      Suspended,
                                      KernelMode,
                                      FALSE,
                                      NULL);

                USBDStatus = Urb->UrbHeader.Status;
            }

            Status = USBPORT_USBDStatusToNtStatus(Urb, USBDStatus);

            if (TransferedLen)
                *TransferedLen = Urb->UrbControlTransfer.TransferBufferLength;

            if (pUSBDStatus)
                *pUSBDStatus = USBDStatus;
        }

        InterlockedDecrement(&DeviceHandle->DeviceHandleLock);
        ExFreePoolWithTag(Urb, USB_PORT_TAG);
    }
    else
    {
        if (pUSBDStatus)
            *pUSBDStatus = USBD_STATUS_INSUFFICIENT_RESOURCES;

        Status = USBPORT_USBDStatusToNtStatus(NULL,
                                              USBD_STATUS_INSUFFICIENT_RESOURCES);
    }

    DPRINT("USBPORT_SendSetupPacket: Status - %x\n", Status);
    return Status;
}

ULONG
NTAPI
USBPORT_GetInterfaceLength(IN PUSB_INTERFACE_DESCRIPTOR iDescriptor,
                           IN ULONG_PTR EndDescriptors)
{
    SIZE_T Length;
    PUSB_ENDPOINT_DESCRIPTOR Descriptor;
    ULONG ix;

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

    Length = iDescriptor->bLength;
    Descriptor = (PUSB_ENDPOINT_DESCRIPTOR)((ULONG_PTR)iDescriptor + Length);

    if (iDescriptor->bNumEndpoints)
    {
        for (ix = 0; ix < iDescriptor->bNumEndpoints; ix++)
        {
            while ((Descriptor->bDescriptorType != USB_ENDPOINT_DESCRIPTOR_TYPE) &&
                   (Descriptor->bLength > 0))
            {
                Length += Descriptor->bLength;
                Descriptor = (PUSB_ENDPOINT_DESCRIPTOR)((ULONG_PTR)Descriptor +
                                                        Descriptor->bLength);
            }

            Length += Descriptor->bLength;
            Descriptor = (PUSB_ENDPOINT_DESCRIPTOR)((ULONG_PTR)Descriptor +
                                                    Descriptor->bLength);
        }
    }

    while (((ULONG_PTR)Descriptor < EndDescriptors) &&
           (Descriptor->bDescriptorType != USB_INTERFACE_DESCRIPTOR_TYPE) &&
           (Descriptor->bLength > 0))
    {
        Length += Descriptor->bLength;
        Descriptor = (PUSB_ENDPOINT_DESCRIPTOR)((ULONG_PTR)Descriptor +
                                                Descriptor->bLength);
    }

    return Length;
}

PUSB_INTERFACE_DESCRIPTOR
NTAPI
USBPORT_ParseConfigurationDescriptor(IN PUSB_CONFIGURATION_DESCRIPTOR ConfigDescriptor,
                                     IN UCHAR InterfaceNumber,
                                     IN UCHAR Alternate,
                                     OUT PUCHAR OutAlternate)
{
    PUSB_CONFIGURATION_DESCRIPTOR TmpDescriptor;
    PUSB_INTERFACE_DESCRIPTOR iDescriptor;
    PUSB_INTERFACE_DESCRIPTOR OutDescriptor = NULL;
    ULONG_PTR Descriptor = (ULONG_PTR)ConfigDescriptor;
    ULONG_PTR EndDescriptors;
    ULONG ix;

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

    if (OutAlternate)
        *OutAlternate = 0;

    for (TmpDescriptor = (PUSB_CONFIGURATION_DESCRIPTOR)((ULONG_PTR)ConfigDescriptor + ConfigDescriptor->bLength);
         TmpDescriptor->bDescriptorType == USB_CONFIGURATION_DESCRIPTOR_TYPE && TmpDescriptor->bDescriptorType > 0;
         TmpDescriptor = (PUSB_CONFIGURATION_DESCRIPTOR)((ULONG_PTR)TmpDescriptor + TmpDescriptor->bLength))
    ;

    iDescriptor = (PUSB_INTERFACE_DESCRIPTOR)TmpDescriptor;

    EndDescriptors = (ULONG_PTR)ConfigDescriptor +
                                ConfigDescriptor->wTotalLength;

    while ((Descriptor < EndDescriptors) &&
           (iDescriptor->bInterfaceNumber != InterfaceNumber))
    {
        Descriptor = (ULONG_PTR)iDescriptor +
                     USBPORT_GetInterfaceLength(iDescriptor, EndDescriptors);

        iDescriptor = (PUSB_INTERFACE_DESCRIPTOR)Descriptor;
    }

    ix = 0;

    while (Descriptor < EndDescriptors &&
           iDescriptor->bInterfaceNumber == InterfaceNumber)
    {
        if (iDescriptor->bAlternateSetting == Alternate)
            OutDescriptor = iDescriptor;

        Descriptor = (ULONG_PTR)iDescriptor +
                     USBPORT_GetInterfaceLength(iDescriptor, EndDescriptors);

        iDescriptor = (PUSB_INTERFACE_DESCRIPTOR)Descriptor;

        ++ix;
    }

    if ((ix > 1) && OutAlternate)
        *OutAlternate = 1;

    return OutDescriptor;
}

USBD_STATUS
NTAPI
USBPORT_OpenInterface(IN PURB Urb,
                      IN PUSBPORT_DEVICE_HANDLE DeviceHandle,
                      IN PDEVICE_OBJECT FdoDevice,
                      IN PUSBPORT_CONFIGURATION_HANDLE ConfigHandle,
                      IN PUSBD_INTERFACE_INFORMATION InterfaceInfo,
                      IN OUT PUSBPORT_INTERFACE_HANDLE *iHandle,
                      IN BOOLEAN IsSetInterface)
{
    PUSB_INTERFACE_DESCRIPTOR InterfaceDescriptor;
    PUSBPORT_INTERFACE_HANDLE InterfaceHandle = NULL;
    PUSBPORT_PIPE_HANDLE PipeHandle;
    PUSB_ENDPOINT_DESCRIPTOR Descriptor;
    PUSBD_PIPE_INFORMATION PipeInfo;
    ULONG NumEndpoints;
    SIZE_T Length;
    SIZE_T HandleLength;
    BOOLEAN IsAllocated = FALSE;
    USHORT MaxPacketSize;
    USHORT wMaxPacketSize;
    ULONG ix;
    USBD_STATUS USBDStatus = USBD_STATUS_SUCCESS;
    NTSTATUS Status;

    DPRINT("USBPORT_OpenInterface: ...\n");

    InterfaceDescriptor = USBPORT_ParseConfigurationDescriptor(ConfigHandle->ConfigurationDescriptor,
                                                               InterfaceInfo->InterfaceNumber,
                                                               InterfaceInfo->AlternateSetting,
                                                               &InterfaceInfo->AlternateSetting);

    NumEndpoints = InterfaceDescriptor->bNumEndpoints;

    Length = FIELD_OFFSET(USBD_INTERFACE_INFORMATION, Pipes) +
             NumEndpoints * sizeof(USBD_PIPE_INFORMATION);

    if (InterfaceInfo->AlternateSetting && IsSetInterface)
    {
        DPRINT1("USBPORT_OpenInterface: InterfaceInfo->AlternateSetting && IsSetInterface !\n");
    }

    if (*iHandle)
    {
        InterfaceHandle = *iHandle;
    }
    else
    {
        HandleLength = FIELD_OFFSET(USBPORT_INTERFACE_HANDLE, PipeHandle) +
                       NumEndpoints * sizeof(USBPORT_PIPE_HANDLE);

        InterfaceHandle = ExAllocatePoolWithTag(NonPagedPool,
                                                HandleLength,
                                                USB_PORT_TAG);

        if (!InterfaceHandle)
        {
            USBDStatus = USBD_STATUS_INSUFFICIENT_RESOURCES;
            goto Exit;
        }

        RtlZeroMemory(InterfaceHandle, HandleLength);

        for (ix = 0; ix < NumEndpoints; ++ix)
        {
            PipeHandle = &InterfaceHandle->PipeHandle[ix];

            PipeHandle->Flags = PIPE_HANDLE_FLAG_CLOSED;
            PipeHandle->Endpoint = NULL;
        }

        IsAllocated = TRUE;
    }

    InterfaceHandle->AlternateSetting = InterfaceInfo->AlternateSetting;

    RtlCopyMemory(&InterfaceHandle->InterfaceDescriptor,
                  InterfaceDescriptor,
                  sizeof(USB_INTERFACE_DESCRIPTOR));

    InterfaceInfo->Class = InterfaceDescriptor->bInterfaceClass;
    InterfaceInfo->SubClass = InterfaceDescriptor->bInterfaceSubClass;
    InterfaceInfo->Protocol = InterfaceDescriptor->bInterfaceProtocol;
    InterfaceInfo->Reserved = 0;
    InterfaceInfo->NumberOfPipes = InterfaceDescriptor->bNumEndpoints;

    Descriptor = (PUSB_ENDPOINT_DESCRIPTOR)((ULONG_PTR)InterfaceDescriptor +
                                            InterfaceDescriptor->bLength);

    for (ix = 0; ix < NumEndpoints; ++ix)
    {
        PipeHandle = &InterfaceHandle->PipeHandle[ix];

        while (Descriptor->bDescriptorType != USB_ENDPOINT_DESCRIPTOR_TYPE)
        {
            if (Descriptor->bLength == 0)
            {
                break;
            }
            else
            {
                Descriptor = (PUSB_ENDPOINT_DESCRIPTOR)((ULONG_PTR)Descriptor +
                                                        Descriptor->bLength);
            }
        }

        if (InterfaceInfo->Pipes[ix].PipeFlags & USBD_PF_CHANGE_MAX_PACKET)
        {
            Descriptor->wMaxPacketSize = InterfaceInfo->Pipes[ix].MaximumPacketSize;
        }

        RtlCopyMemory(&PipeHandle->EndpointDescriptor,
                      Descriptor,
                      sizeof(USB_ENDPOINT_DESCRIPTOR));

        PipeHandle->Flags = PIPE_HANDLE_FLAG_CLOSED;
        PipeHandle->PipeFlags = InterfaceInfo->Pipes[ix].PipeFlags;
        PipeHandle->Endpoint = NULL;

        wMaxPacketSize = Descriptor->wMaxPacketSize;

        /* USB 2.0 Specification, 5.9 High-Speed, High Bandwidth Endpoints */
        MaxPacketSize = (wMaxPacketSize & 0x7FF) * (((wMaxPacketSize >> 11) & 3) + 1);

        InterfaceInfo->Pipes[ix].EndpointAddress = Descriptor->bEndpointAddress;
        InterfaceInfo->Pipes[ix].PipeType = Descriptor->bmAttributes & USB_ENDPOINT_TYPE_MASK;
        InterfaceInfo->Pipes[ix].MaximumPacketSize = MaxPacketSize;
        InterfaceInfo->Pipes[ix].PipeHandle = (USBD_PIPE_HANDLE)-1;
        InterfaceInfo->Pipes[ix].Interval = Descriptor->bInterval;

        Descriptor = (PUSB_ENDPOINT_DESCRIPTOR)((ULONG_PTR)Descriptor +
                                                Descriptor->bLength);
    }

    if (USBD_SUCCESS(USBDStatus))
    {
        for (ix = 0; ix < NumEndpoints; ++ix)
        {
            PipeInfo = &InterfaceInfo->Pipes[ix];
            PipeHandle = &InterfaceHandle->PipeHandle[ix];

            Status = USBPORT_OpenPipe(FdoDevice,
                                      DeviceHandle,
                                      PipeHandle,
                                      &USBDStatus);

            if (!NT_SUCCESS(Status))
                break;

            PipeInfo->PipeHandle = PipeHandle;
        }

        if (NumEndpoints)
        {
            USBPORT_USBDStatusToNtStatus(Urb, USBDStatus);
        }
    }

Exit:

    if (USBD_SUCCESS(USBDStatus))
    {
        InterfaceInfo->InterfaceHandle = InterfaceHandle;
        *iHandle = InterfaceHandle;
        InterfaceInfo->Length = Length;
    }
    else
    {
        if (InterfaceHandle)
        {
            if (NumEndpoints)
            {
                DPRINT1("USBPORT_OpenInterface: USBDStatus - %lx\n", USBDStatus);
            }

            if (IsAllocated)
                ExFreePoolWithTag(InterfaceHandle, USB_PORT_TAG);
        }
    }

    return USBDStatus;
}

VOID
NTAPI
USBPORT_CloseConfiguration(IN PUSBPORT_DEVICE_HANDLE DeviceHandle,
                           IN PDEVICE_OBJECT FdoDevice)
{
    PUSBPORT_CONFIGURATION_HANDLE ConfigHandle;
    PLIST_ENTRY iHandleList;
    PUSBPORT_INTERFACE_HANDLE iHandle;
    ULONG NumEndpoints;
    PUSBPORT_PIPE_HANDLE PipeHandle;

    DPRINT("USBPORT_CloseConfiguration: ... \n");

    ConfigHandle = DeviceHandle->ConfigHandle;

    if (ConfigHandle)
    {
        iHandleList = &ConfigHandle->InterfaceHandleList;

        while (!IsListEmpty(iHandleList))
        {
            iHandle = CONTAINING_RECORD(iHandleList->Flink,
                                        USBPORT_INTERFACE_HANDLE,
                                        InterfaceLink);

            DPRINT("USBPORT_CloseConfiguration: iHandle - %p\n", iHandle);

            RemoveHeadList(iHandleList);

            NumEndpoints = iHandle->InterfaceDescriptor.bNumEndpoints;

            PipeHandle = &iHandle->PipeHandle[0];

            while (NumEndpoints > 0)
            {
                USBPORT_ClosePipe(DeviceHandle, FdoDevice, PipeHandle);
                PipeHandle += 1;
                --NumEndpoints;
            }

            ExFreePoolWithTag(iHandle, USB_PORT_TAG);
        }

        ExFreePoolWithTag(ConfigHandle, USB_PORT_TAG);
        DeviceHandle->ConfigHandle = NULL;
    }
}

NTSTATUS
NTAPI
USBPORT_InitInterfaceInfo(IN PUSBD_INTERFACE_INFORMATION InterfaceInfo,
                          IN PUSBPORT_CONFIGURATION_HANDLE ConfigHandle)
{
    PUSB_INTERFACE_DESCRIPTOR Descriptor;
    PUSBD_PIPE_INFORMATION Pipe;
    USHORT Length;
    ULONG PipeFlags;
    ULONG NumberOfPipes;
    USBD_STATUS USBDStatus = USBD_STATUS_SUCCESS;

    DPRINT("USBPORT_InitInterfaceInfo: InterfaceInfo - %p, ConfigHandle - %p\n",
           InterfaceInfo,
           ConfigHandle);

    Descriptor = USBPORT_ParseConfigurationDescriptor(ConfigHandle->ConfigurationDescriptor,
                                                      InterfaceInfo->InterfaceNumber,
                                                      InterfaceInfo->AlternateSetting,
                                                      &InterfaceInfo->AlternateSetting);

    Length = sizeof(USBD_INTERFACE_INFORMATION) +
             sizeof(USBD_PIPE_INFORMATION);

    if (Descriptor)
    {
        NumberOfPipes = Descriptor->bNumEndpoints;

        Length = FIELD_OFFSET(USBD_INTERFACE_INFORMATION, Pipes) +
                 NumberOfPipes * sizeof(USBD_PIPE_INFORMATION);

        if (InterfaceInfo->Length >= Length)
        {
            InterfaceInfo->Class = 0;
            InterfaceInfo->SubClass = 0;
            InterfaceInfo->Protocol = 0;
            InterfaceInfo->Reserved = 0;
            InterfaceInfo->InterfaceHandle = 0;
            InterfaceInfo->NumberOfPipes = NumberOfPipes;

            Pipe = InterfaceInfo->Pipes;

            while (NumberOfPipes > 0)
            {
                Pipe->EndpointAddress = 0;
                Pipe->Interval = 0;
                Pipe->PipeType = 0;
                Pipe->PipeHandle = 0;

                PipeFlags = Pipe->PipeFlags;

                if (PipeFlags & ~USBD_PF_VALID_MASK)
                    USBDStatus = USBD_STATUS_INVALID_PIPE_FLAGS;

                if (!(PipeFlags & USBD_PF_CHANGE_MAX_PACKET))
                    Pipe->MaximumPacketSize = 0;

                Pipe += 1;
                --NumberOfPipes;
            }
        }
        else
        {
            USBDStatus = USBD_STATUS_BUFFER_TOO_SMALL;
        }
    }
    else
    {
        USBDStatus = USBD_STATUS_INTERFACE_NOT_FOUND;
    }

    InterfaceInfo->Length = Length;
    return USBDStatus;
}

NTSTATUS
NTAPI
USBPORT_HandleSelectConfiguration(IN PDEVICE_OBJECT FdoDevice,
                                  IN PIRP Irp,
                                  IN PURB Urb)
{
    PUSB_CONFIGURATION_DESCRIPTOR ConfigDescriptor;
    PUSBPORT_DEVICE_HANDLE DeviceHandle;
    PUSBPORT_CONFIGURATION_HANDLE ConfigHandle = NULL;
    PUSBD_INTERFACE_INFORMATION InterfaceInfo;
    PUSBPORT_INTERFACE_HANDLE InterfaceHandle;
    ULONG iNumber;
    ULONG ix;
    USB_DEFAULT_PIPE_SETUP_PACKET SetupPacket;
    NTSTATUS Status;
    USBD_STATUS USBDStatus;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;

    DPRINT("USBPORT_HandleSelectConfiguration: ConfigDescriptor %p\n",
           Urb->UrbSelectConfiguration.ConfigurationDescriptor);

    FdoExtension = FdoDevice->DeviceExtension;

    KeWaitForSingleObject(&FdoExtension->DeviceSemaphore,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);

    DeviceHandle = Urb->UrbHeader.UsbdDeviceHandle;
    ConfigDescriptor = Urb->UrbSelectConfiguration.ConfigurationDescriptor;

    if (!ConfigDescriptor)
    {
        DPRINT("USBPORT_HandleSelectConfiguration: ConfigDescriptor == NULL\n");

        RtlZeroMemory(&SetupPacket, sizeof(USB_DEFAULT_PIPE_SETUP_PACKET));

        SetupPacket.bmRequestType.B = 0;
        SetupPacket.bRequest = USB_REQUEST_SET_CONFIGURATION;
        SetupPacket.wValue.W = 0;
        SetupPacket.wIndex.W = 0;
        SetupPacket.wLength = 0;

        USBPORT_SendSetupPacket(DeviceHandle,
                                FdoDevice,
                                &SetupPacket,
                                NULL,
                                0,
                                NULL,
                                NULL);

        Status = USBPORT_USBDStatusToNtStatus(Urb, USBD_STATUS_SUCCESS);
        goto Exit;
    }

    USBPORT_DumpingConfiguration(ConfigDescriptor);

    InterfaceInfo = &Urb->UrbSelectConfiguration.Interface;

    iNumber = 0;

    do
    {
        ++iNumber;
        InterfaceInfo = (PUSBD_INTERFACE_INFORMATION)
                        ((ULONG_PTR)InterfaceInfo +
                         InterfaceInfo->Length);
    }
    while ((ULONG_PTR)InterfaceInfo < (ULONG_PTR)Urb + Urb->UrbHeader.Length);

    if ((iNumber <= 0) || (iNumber != ConfigDescriptor->bNumInterfaces))
    {
        Status = USBPORT_USBDStatusToNtStatus(Urb,
                                              USBD_STATUS_INVALID_CONFIGURATION_DESCRIPTOR);
        goto Exit;
    }

    ConfigHandle = ExAllocatePoolWithTag(NonPagedPool,
                                         ConfigDescriptor->wTotalLength + sizeof(USBPORT_CONFIGURATION_HANDLE),
                                         USB_PORT_TAG);

    if (!ConfigHandle)
    {
        Status = USBPORT_USBDStatusToNtStatus(Urb,
                                              USBD_STATUS_INSUFFICIENT_RESOURCES);
        goto Exit;
    }

    RtlZeroMemory(ConfigHandle,
                  ConfigDescriptor->wTotalLength + sizeof(USBPORT_CONFIGURATION_HANDLE));

    InitializeListHead(&ConfigHandle->InterfaceHandleList);

    ConfigHandle->ConfigurationDescriptor = (PUSB_CONFIGURATION_DESCRIPTOR)(ConfigHandle + 1);

    RtlCopyMemory(ConfigHandle->ConfigurationDescriptor,
                  ConfigDescriptor,
                  ConfigDescriptor->wTotalLength);

    RtlZeroMemory(&SetupPacket, sizeof(USB_DEFAULT_PIPE_SETUP_PACKET));

    SetupPacket.bmRequestType.B = 0;
    SetupPacket.bRequest = USB_REQUEST_SET_CONFIGURATION;
    SetupPacket.wValue.W = ConfigDescriptor->bConfigurationValue;
    SetupPacket.wIndex.W = 0;
    SetupPacket.wLength = 0;

    USBPORT_SendSetupPacket(DeviceHandle,
                            FdoDevice,
                            &SetupPacket,
                            NULL,
                            0,
                            NULL,
                            &USBDStatus);

    if (USBD_ERROR(USBDStatus))
    {
        Status = USBPORT_USBDStatusToNtStatus(Urb,
                                              USBD_STATUS_SET_CONFIG_FAILED);
        goto Exit;
    }

    if (iNumber <= 0)
    {
        Status = USBPORT_USBDStatusToNtStatus(Urb,
                                              USBD_STATUS_SUCCESS);

        goto Exit;
    }

    InterfaceInfo = &Urb->UrbSelectConfiguration.Interface;

    for (ix = 0; ix < iNumber; ++ix)
    {
        USBDStatus = USBPORT_InitInterfaceInfo(InterfaceInfo,
                                               ConfigHandle);

        InterfaceHandle = NULL;

        if (USBD_SUCCESS(USBDStatus))
        {
            USBDStatus = USBPORT_OpenInterface(Urb,
                                               DeviceHandle,
                                               FdoDevice,
                                               ConfigHandle,
                                               InterfaceInfo,
                                               &InterfaceHandle,
                                               TRUE);
        }

        if (InterfaceHandle)
        {
            InsertTailList(&ConfigHandle->InterfaceHandleList,
                           &InterfaceHandle->InterfaceLink);
        }

        if (USBD_ERROR(USBDStatus))
            break;

        InterfaceInfo = (PUSBD_INTERFACE_INFORMATION)
                         ((ULONG_PTR)InterfaceInfo +
                          InterfaceInfo->Length);
    }

    if (ix >= iNumber)
    {
        Status = USBPORT_USBDStatusToNtStatus(Urb,
                                              USBD_STATUS_SUCCESS);
    }
    else
    {
        Status = USBPORT_USBDStatusToNtStatus(Urb, USBDStatus);
    }

Exit:

    if (NT_SUCCESS(Status))
    {
        Urb->UrbSelectConfiguration.ConfigurationHandle = ConfigHandle;
        DeviceHandle->ConfigHandle = ConfigHandle;
    }
    else
    {
        DPRINT1("USBPORT_HandleSelectConfiguration: Status %x\n", Status);
    }

    KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                       LOW_REALTIME_PRIORITY,
                       1,
                       FALSE);

    return Status;
}

VOID
NTAPI
USBPORT_AddDeviceHandle(IN PDEVICE_OBJECT FdoDevice,
                        IN PUSBPORT_DEVICE_HANDLE DeviceHandle)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;

    DPRINT("USBPORT_AddDeviceHandle: ... \n");

    FdoExtension = FdoDevice->DeviceExtension;

    InsertTailList(&FdoExtension->DeviceHandleList,
                   &DeviceHandle->DeviceHandleLink);
}

VOID
NTAPI
USBPORT_RemoveDeviceHandle(IN PDEVICE_OBJECT FdoDevice,
                           IN PUSBPORT_DEVICE_HANDLE DeviceHandle)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    KIRQL OldIrql;

    DPRINT("USBPORT_RemoveDeviceHandle \n");

    FdoExtension = FdoDevice->DeviceExtension;

    KeAcquireSpinLock(&FdoExtension->DeviceHandleSpinLock, &OldIrql);
    RemoveEntryList(&DeviceHandle->DeviceHandleLink);
    KeReleaseSpinLock(&FdoExtension->DeviceHandleSpinLock, OldIrql);
}

BOOLEAN
NTAPI
USBPORT_ValidateDeviceHandle(IN PDEVICE_OBJECT FdoDevice,
                             IN PUSBPORT_DEVICE_HANDLE DeviceHandle)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    KIRQL OldIrql;
    PLIST_ENTRY HandleList;
    PUSBPORT_DEVICE_HANDLE CurrentHandle;
    BOOLEAN Result = FALSE;

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

    FdoExtension = FdoDevice->DeviceExtension;

    KeAcquireSpinLock(&FdoExtension->DeviceHandleSpinLock, &OldIrql);
    if (DeviceHandle)
    {
        HandleList = FdoExtension->DeviceHandleList.Flink;

        while (HandleList != &FdoExtension->DeviceHandleList)
        {
            CurrentHandle = CONTAINING_RECORD(HandleList,
                                              USBPORT_DEVICE_HANDLE,
                                              DeviceHandleLink);

            if (CurrentHandle == DeviceHandle)
            {
                Result = TRUE;
                break;
            }

            HandleList = HandleList->Flink;
        }
    }
    KeReleaseSpinLock(&FdoExtension->DeviceHandleSpinLock, OldIrql);

    return Result;
}

BOOLEAN
NTAPI
USBPORT_DeviceHasTransfers(IN PDEVICE_OBJECT FdoDevice,
                           IN PUSBPORT_DEVICE_HANDLE DeviceHandle)
{
    PLIST_ENTRY PipeHandleList;
    PUSBPORT_PIPE_HANDLE PipeHandle;

    DPRINT("USBPORT_DeviceHasTransfers: ... \n");

    PipeHandleList = DeviceHandle->PipeHandleList.Flink;

    while (PipeHandleList != &DeviceHandle->PipeHandleList)
    {
        PipeHandle = CONTAINING_RECORD(PipeHandleList,
                                       USBPORT_PIPE_HANDLE,
                                       PipeLink);

        PipeHandleList = PipeHandleList->Flink;

        if (!(PipeHandle->Flags & PIPE_HANDLE_FLAG_NULL_PACKET_SIZE) &&
            USBPORT_EndpointHasQueuedTransfers(FdoDevice, PipeHandle->Endpoint, NULL))
        {
            return TRUE;
        }
    }

    return FALSE;
}

VOID
NTAPI
USBPORT_AbortTransfers(IN PDEVICE_OBJECT FdoDevice,
                       IN PUSBPORT_DEVICE_HANDLE DeviceHandle)
{
    PLIST_ENTRY HandleList;
    PUSBPORT_PIPE_HANDLE PipeHandle;
    BOOLEAN Result;

    DPRINT("USBPORT_AbortAllTransfers: ... \n");

    HandleList = DeviceHandle->PipeHandleList.Flink;

    while (HandleList != &DeviceHandle->PipeHandleList)
    {
        PipeHandle = CONTAINING_RECORD(HandleList,
                                       USBPORT_PIPE_HANDLE,
                                       PipeLink);

        HandleList = HandleList->Flink;

        if (!(PipeHandle->Flags & DEVICE_HANDLE_FLAG_ROOTHUB))
        {
            PipeHandle->Endpoint->Flags |= ENDPOINT_FLAG_ABORTING;

            USBPORT_AbortEndpoint(FdoDevice, PipeHandle->Endpoint, NULL);
            USBPORT_FlushMapTransfers(FdoDevice);
        }
    }

    while (TRUE)
    {
        Result = USBPORT_DeviceHasTransfers(FdoDevice, DeviceHandle);

        if (!Result)
            break;

        USBPORT_Wait(FdoDevice, 100);
    }
}

PUSB2_TT_EXTENSION
NTAPI 
USBPORT_GetTt(IN PDEVICE_OBJECT FdoDevice,
              IN PUSBPORT_DEVICE_HANDLE HubDeviceHandle,
              OUT PUSHORT OutPort,
              OUT PUSBPORT_DEVICE_HANDLE * OutHubDeviceHandle)
{
    PUSBPORT_DEVICE_HANDLE DeviceHandle = HubDeviceHandle;
    ULONG TtCount;
    PLIST_ENTRY Entry;
    PUSB2_TT_EXTENSION TtExtension = NULL;

    DPRINT("USBPORT_GetTt: HubDeviceHandle - %p\n", HubDeviceHandle);

    *OutHubDeviceHandle = NULL;

    while (DeviceHandle->DeviceSpeed != UsbHighSpeed)
    {
        DPRINT("USBPORT_GetTt: DeviceHandle - %p, DeviceHandle->PortNumber - %X\n",
               DeviceHandle,
               DeviceHandle->PortNumber);

        *OutPort = DeviceHandle->PortNumber;

        DeviceHandle = DeviceHandle->HubDeviceHandle;

        if (!DeviceHandle)
            return NULL;
    }

    TtCount = DeviceHandle->TtCount;

    if (!TtCount)
        return NULL;

    if (IsListEmpty(&DeviceHandle->TtList))
        return NULL;

    Entry = DeviceHandle->TtList.Flink;

    if (TtCount > 1)
    {
        while (Entry != &DeviceHandle->TtList)
        {
            ASSERT(Entry != NULL);

            TtExtension = CONTAINING_RECORD(Entry,
                                            USB2_TT_EXTENSION,
                                            Link);

            if (TtExtension->TtNumber == *OutPort)
                break;

            Entry = Entry->Flink;

            TtExtension = NULL;
        }
    }
    else
    {
        TtExtension = CONTAINING_RECORD(Entry,
                                        USB2_TT_EXTENSION,
                                        Link);
    }

    *OutHubDeviceHandle = DeviceHandle;

    return TtExtension;
}

NTSTATUS
NTAPI
USBPORT_CreateDevice(IN OUT PUSB_DEVICE_HANDLE *pUsbdDeviceHandle,
                     IN PDEVICE_OBJECT FdoDevice,
                     IN PUSBPORT_DEVICE_HANDLE HubDeviceHandle,
                     IN USHORT PortStatus,
                     IN USHORT Port)
{
    PUSBPORT_DEVICE_HANDLE TtDeviceHandle = NULL;
    PUSB2_TT_EXTENSION TtExtension = NULL;
    USHORT port;
    PUSBPORT_DEVICE_HANDLE DeviceHandle;
    PUSBPORT_PIPE_HANDLE PipeHandle;
    BOOL IsOpenedPipe;
    PVOID DeviceDescriptor;
    USB_DEFAULT_PIPE_SETUP_PACKET SetupPacket;
    SIZE_T TransferedLen;
    SIZE_T DescriptorMinSize;
    UCHAR MaxPacketSize;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSBPORT_REGISTRATION_PACKET Packet;
    NTSTATUS Status;

    DPRINT("USBPORT_CreateDevice: PortStatus - %p, Port - %x\n",
           PortStatus,
           Port);

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

    KeWaitForSingleObject(&FdoExtension->DeviceSemaphore,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);

    if (!USBPORT_ValidateDeviceHandle(FdoDevice, HubDeviceHandle))
    {
        KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                           LOW_REALTIME_PRIORITY,
                           1,
                           FALSE);

        DPRINT1("USBPORT_CreateDevice: Not valid hub DeviceHandle\n");
        return STATUS_DEVICE_NOT_CONNECTED;
    }

    port = Port;

    if (Packet->MiniPortFlags & USB_MINIPORT_FLAGS_USB2 &&
        !(PortStatus & USB_PORT_STATUS_HIGH_SPEED))
    {
        DPRINT1("USBPORT_CreateDevice: USB1 device connected to USB2 port\n");

        TtExtension = USBPORT_GetTt(FdoDevice,
                                    HubDeviceHandle,
                                    &port,
                                    &TtDeviceHandle);

        DPRINT("USBPORT_CreateDevice: TtDeviceHandle - %p, port - %x\n",
               TtDeviceHandle,
               port);
    }

    KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                       LOW_REALTIME_PRIORITY,
                       1,
                       FALSE);

    DeviceHandle = ExAllocatePoolWithTag(NonPagedPool,
                                         sizeof(USBPORT_DEVICE_HANDLE),
                                         USB_PORT_TAG);

    if (!DeviceHandle)
    {
        DPRINT1("USBPORT_CreateDevice: Not allocated DeviceHandle\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    RtlZeroMemory(DeviceHandle, sizeof(USBPORT_DEVICE_HANDLE));

    *pUsbdDeviceHandle = NULL;

    DeviceHandle->TtExtension = TtExtension;
    DeviceHandle->PortNumber = Port;
    DeviceHandle->HubDeviceHandle = HubDeviceHandle;

    if (PortStatus & USB_PORT_STATUS_LOW_SPEED)
    {
        DeviceHandle->DeviceSpeed = UsbLowSpeed;
    }
    else if (PortStatus & USB_PORT_STATUS_HIGH_SPEED)
    {
        DeviceHandle->DeviceSpeed = UsbHighSpeed;
    }
    else
    {
        DeviceHandle->DeviceSpeed = UsbFullSpeed;
    }

    KeWaitForSingleObject(&FdoExtension->DeviceSemaphore,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);

    PipeHandle = &DeviceHandle->PipeHandle;

    PipeHandle->Flags = PIPE_HANDLE_FLAG_CLOSED;

    PipeHandle->EndpointDescriptor.bLength = sizeof(PipeHandle->EndpointDescriptor);
    PipeHandle->EndpointDescriptor.bDescriptorType = USB_ENDPOINT_DESCRIPTOR_TYPE;

    if (DeviceHandle->DeviceSpeed == UsbLowSpeed)
    {
        PipeHandle->EndpointDescriptor.wMaxPacketSize = 8;
    }
    else
    {
        PipeHandle->EndpointDescriptor.wMaxPacketSize = USB_DEFAULT_MAX_PACKET;
    }

    InitializeListHead(&DeviceHandle->PipeHandleList);
    InitializeListHead(&DeviceHandle->TtList);

    Status = USBPORT_OpenPipe(FdoDevice,
                              DeviceHandle,
                              PipeHandle,
                              NULL);

    IsOpenedPipe = NT_SUCCESS(Status);

    if (NT_ERROR(Status))
    {
        DPRINT1("USBPORT_CreateDevice: USBPORT_OpenPipe return - %lx\n", Status);

        KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                           LOW_REALTIME_PRIORITY,
                           1,
                           FALSE);

        ExFreePoolWithTag(DeviceHandle, USB_PORT_TAG);

        return Status;
    }

    DeviceDescriptor = ExAllocatePoolWithTag(NonPagedPool,
                                             USB_DEFAULT_MAX_PACKET,
                                             USB_PORT_TAG);

    if (!DeviceDescriptor)
    {
        DPRINT1("USBPORT_CreateDevice: Not allocated DeviceDescriptor\n");
        goto ErrorExit;
    }

    RtlZeroMemory(DeviceDescriptor, USB_DEFAULT_MAX_PACKET);
    RtlZeroMemory(&SetupPacket, sizeof(USB_DEFAULT_PIPE_SETUP_PACKET));

    SetupPacket.bmRequestType.Dir = BMREQUEST_DEVICE_TO_HOST;
    SetupPacket.bRequest = USB_REQUEST_GET_DESCRIPTOR;
    SetupPacket.wValue.HiByte = USB_DEVICE_DESCRIPTOR_TYPE;
    SetupPacket.wLength = USB_DEFAULT_MAX_PACKET;

    TransferedLen = 0;

    Status = USBPORT_SendSetupPacket(DeviceHandle,
                                     FdoDevice,
                                     &SetupPacket,
                                     DeviceDescriptor,
                                     USB_DEFAULT_MAX_PACKET,
                                     &TransferedLen,
                                     NULL);

    RtlCopyMemory(&DeviceHandle->DeviceDescriptor,
                  DeviceDescriptor,
                  sizeof(USB_DEVICE_DESCRIPTOR));

    ExFreePoolWithTag(DeviceDescriptor, USB_PORT_TAG);

    DescriptorMinSize = RTL_SIZEOF_THROUGH_FIELD(USB_DEVICE_DESCRIPTOR,
                                                 bMaxPacketSize0);

    if ((TransferedLen == DescriptorMinSize) && !NT_SUCCESS(Status))
    {
        Status = STATUS_SUCCESS;
    }

    if (NT_SUCCESS(Status) && (TransferedLen >= DescriptorMinSize))
    {
        if ((DeviceHandle->DeviceDescriptor.bLength >= sizeof(USB_DEVICE_DESCRIPTOR)) &&
            (DeviceHandle->DeviceDescriptor.bDescriptorType == USB_DEVICE_DESCRIPTOR_TYPE))
        {
            MaxPacketSize = DeviceHandle->DeviceDescriptor.bMaxPacketSize0;

            if (MaxPacketSize == 8 ||
                MaxPacketSize == 16 ||
                MaxPacketSize == 32 ||
                MaxPacketSize == 64)
            {
                USBPORT_AddDeviceHandle(FdoDevice, DeviceHandle);

                *pUsbdDeviceHandle = DeviceHandle;

                KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                                   LOW_REALTIME_PRIORITY,
                                   1,
                                   FALSE);

                return Status;
            }
        }
    }

    DPRINT1("USBPORT_CreateDevice: ERROR!!! TransferedLen - %x, Status - %lx\n",
            TransferedLen,
            Status);

ErrorExit:

    if (TtExtension && TtDeviceHandle)
    {
        SetupPacket.bmRequestType.Recipient = BMREQUEST_TO_OTHER;
        SetupPacket.bmRequestType.Reserved = 0;
        SetupPacket.bmRequestType.Type = BMREQUEST_CLASS;
        SetupPacket.bmRequestType.Dir = BMREQUEST_HOST_TO_DEVICE;

        /* Table 11-15.  Hub Class Requests */
        if (TtDeviceHandle == HubDeviceHandle)
        {
            SetupPacket.bRequest = USB_REQUEST_RESET_TT;
        }
        else
        {
            SetupPacket.bRequest = USB_REQUEST_CLEAR_TT_BUFFER;
        }

        SetupPacket.wValue.LowByte = 0;
        SetupPacket.wValue.HiByte = 0;
        SetupPacket.wIndex.W = port;
        SetupPacket.wLength = 0;

        USBPORT_SendSetupPacket(TtDeviceHandle,
                                FdoDevice,
                                &SetupPacket,
                                NULL,
                                0,
                                NULL,
                                NULL);
    }

    Status = STATUS_DEVICE_DATA_ERROR;

    if (IsOpenedPipe)
    {
        USBPORT_ClosePipe(DeviceHandle, FdoDevice, PipeHandle);
    }

    KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                       LOW_REALTIME_PRIORITY,
                       1,
                       FALSE);

    ExFreePoolWithTag(DeviceHandle, USB_PORT_TAG);

    return Status;
}

ULONG
NTAPI
USBPORT_AllocateUsbAddress(IN PDEVICE_OBJECT FdoDevice)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    ULONG BitMapIdx;
    ULONG BitNumber;
    ULONG ix;

    DPRINT("USBPORT_AllocateUsbAddress \n");

    FdoExtension = FdoDevice->DeviceExtension;

    for (ix = 0; ix < 4; ++ix)
    {
        BitMapIdx = 1;

        for (BitNumber = 0; BitNumber < 32; ++BitNumber)
        {
            if (!(FdoExtension->UsbAddressBitMap[ix] & BitMapIdx))
            {
                FdoExtension->UsbAddressBitMap[ix] |= BitMapIdx;
                return 32 * ix + BitNumber;
            }

            BitMapIdx <<= 2;
        }
    }

    return 0;
}

VOID
NTAPI
USBPORT_FreeUsbAddress(IN PDEVICE_OBJECT FdoDevice,
                       IN USHORT DeviceAddress)
{
    PUSBPORT_DEVICE_EXTENSION  FdoExtension;
    ULONG ix;
    ULONG BitMapIdx;
    ULONG BitNumber;
    USHORT CurrentAddress;

    DPRINT("USBPORT_FreeUsbAddress: DeviceAddress - %x\n", DeviceAddress);

    FdoExtension = FdoDevice->DeviceExtension;

    for (ix = 0; ix < 4; ++ix)
    {
        BitMapIdx = 1;
        CurrentAddress = 32 * ix;

        for (BitNumber = 0; BitNumber < 32; ++BitNumber)
        {
            if (CurrentAddress == DeviceAddress)
            {
                FdoExtension->UsbAddressBitMap[ix] &= ~BitMapIdx;
                return;
            }

            BitMapIdx <<= 2;
            CurrentAddress++;
        }
    }
}

NTSTATUS
NTAPI
USBPORT_InitializeDevice(IN PUSBPORT_DEVICE_HANDLE DeviceHandle,
                         IN PDEVICE_OBJECT FdoDevice)
{
    PUSBPORT_ENDPOINT Endpoint;
    USB_DEFAULT_PIPE_SETUP_PACKET CtrlSetup;
    ULONG TransferedLen;
    USHORT DeviceAddress = 0;
    UCHAR MaxPacketSize;
    NTSTATUS Status;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;

    DPRINT("USBPORT_InitializeDevice: ... \n");

    ASSERT(DeviceHandle != NULL);

    FdoExtension = FdoDevice->DeviceExtension;

    KeWaitForSingleObject(&FdoExtension->DeviceSemaphore,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);

    DeviceAddress = USBPORT_AllocateUsbAddress(FdoDevice);
    ASSERT(DeviceHandle->DeviceAddress == USB_DEFAULT_DEVICE_ADDRESS);

    RtlZeroMemory(&CtrlSetup, sizeof(USB_DEFAULT_PIPE_SETUP_PACKET));

    CtrlSetup.bRequest = USB_REQUEST_SET_ADDRESS;
    CtrlSetup.wValue.W = DeviceAddress;

    Status = USBPORT_SendSetupPacket(DeviceHandle,
                                     FdoDevice,
                                     &CtrlSetup,
                                     NULL,
                                     0,
                                     NULL,
                                     NULL);

    DPRINT("USBPORT_InitializeDevice: DeviceAddress - %x. SendSetupPacket Status - %x\n",
           DeviceAddress,
           Status);

    if (!NT_SUCCESS(Status))
        goto ExitError;

    DeviceHandle->DeviceAddress = DeviceAddress;
    Endpoint = DeviceHandle->PipeHandle.Endpoint;

    Endpoint->EndpointProperties.TotalMaxPacketSize = DeviceHandle->DeviceDescriptor.bMaxPacketSize0;
    Endpoint->EndpointProperties.DeviceAddress = DeviceAddress;

    Status = USBPORT_ReopenPipe(FdoDevice, Endpoint);

    if (!NT_SUCCESS(Status))
        goto ExitError;

    USBPORT_Wait(FdoDevice, 10);

    RtlZeroMemory(&CtrlSetup, sizeof(USB_DEFAULT_PIPE_SETUP_PACKET));

    CtrlSetup.bRequest = USB_REQUEST_GET_DESCRIPTOR;
    CtrlSetup.wValue.HiByte = USB_DEVICE_DESCRIPTOR_TYPE;
    CtrlSetup.wLength = sizeof(USB_DEVICE_DESCRIPTOR);
    CtrlSetup.bmRequestType.B = 0x80;

    Status = USBPORT_SendSetupPacket(DeviceHandle,
                                     FdoDevice,
                                     &CtrlSetup,
                                     &DeviceHandle->DeviceDescriptor,
                                     sizeof(USB_DEVICE_DESCRIPTOR),
                                     &TransferedLen,
                                     NULL);

    if (NT_SUCCESS(Status))
    {
        ASSERT(TransferedLen == sizeof(USB_DEVICE_DESCRIPTOR));
        ASSERT(DeviceHandle->DeviceDescriptor.bLength >= sizeof(USB_DEVICE_DESCRIPTOR));
        ASSERT(DeviceHandle->DeviceDescriptor.bDescriptorType == USB_DEVICE_DESCRIPTOR_TYPE);

        MaxPacketSize = DeviceHandle->DeviceDescriptor.bMaxPacketSize0;

        ASSERT((MaxPacketSize == 8) ||
               (MaxPacketSize == 16) ||
               (MaxPacketSize == 32) ||
               (MaxPacketSize == 64));

        if (DeviceHandle->DeviceSpeed == UsbHighSpeed &&
            DeviceHandle->DeviceDescriptor.bDeviceClass == USB_DEVICE_CLASS_HUB)
        {
            DeviceHandle->Flags |= DEVICE_HANDLE_FLAG_USB2HUB;
        }
    }
    else
    {
ExitError:
        DPRINT1("USBPORT_InitializeDevice: ExitError. Status - %x\n", Status);
    }

    KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                       LOW_REALTIME_PRIORITY,
                       1,
                       FALSE);

    return Status;
}

NTSTATUS
NTAPI
USBPORT_GetUsbDescriptor(IN PUSBPORT_DEVICE_HANDLE DeviceHandle,
                         IN PDEVICE_OBJECT FdoDevice,
                         IN UCHAR Type,
                         IN PUCHAR ConfigDesc,
                         IN PULONG ConfigDescSize)
{
    USB_DEFAULT_PIPE_SETUP_PACKET SetupPacket;

    DPRINT("USBPORT_GetUsbDescriptor: Type - %x\n");

    RtlZeroMemory(&SetupPacket, sizeof(USB_DEFAULT_PIPE_SETUP_PACKET));

    SetupPacket.bmRequestType.Dir = BMREQUEST_DEVICE_TO_HOST;
    SetupPacket.bRequest = USB_REQUEST_GET_DESCRIPTOR;
    SetupPacket.wValue.HiByte = Type;
    SetupPacket.wLength = (USHORT)*ConfigDescSize;

    return USBPORT_SendSetupPacket(DeviceHandle,
                                   FdoDevice,
                                   &SetupPacket,
                                   ConfigDesc,
                                   *ConfigDescSize,
                                   ConfigDescSize,
                                   NULL);
}

PUSBPORT_INTERFACE_HANDLE
NTAPI
USBPORT_GetInterfaceHandle(IN PUSBPORT_CONFIGURATION_HANDLE ConfigurationHandle,
                           IN UCHAR InterfaceNumber)
{
    PUSBPORT_INTERFACE_HANDLE InterfaceHandle;
    PLIST_ENTRY iHandleList;
    UCHAR InterfaceNum;

    DPRINT("USBPORT_GetInterfaceHandle: ConfigurationHandle - %p, InterfaceNumber - %p\n",
           ConfigurationHandle,
           InterfaceNumber);

    iHandleList = ConfigurationHandle->InterfaceHandleList.Flink;

    while (iHandleList &&
           (iHandleList != &ConfigurationHandle->InterfaceHandleList))
    {
        InterfaceHandle = CONTAINING_RECORD(iHandleList,
                                            USBPORT_INTERFACE_HANDLE,
                                            InterfaceLink);

        InterfaceNum = InterfaceHandle->InterfaceDescriptor.bInterfaceNumber;

        if (InterfaceNum == InterfaceNumber)
            return InterfaceHandle;

        iHandleList = InterfaceHandle->InterfaceLink.Flink;
    }

    return NULL;
}

NTSTATUS
NTAPI
USBPORT_HandleSelectInterface(IN PDEVICE_OBJECT FdoDevice,
                              IN PIRP Irp,
                              IN PURB Urb)
{
    PUSBPORT_DEVICE_HANDLE DeviceHandle;
    PUSBPORT_CONFIGURATION_HANDLE ConfigurationHandle;
    PUSBD_INTERFACE_INFORMATION Interface;
    PUSBPORT_INTERFACE_HANDLE InterfaceHandle;
    PUSBPORT_INTERFACE_HANDLE iHandle;
    PUSBPORT_PIPE_HANDLE PipeHandle;
    USBD_STATUS USBDStatus;
    USHORT Length;
    ULONG ix;
    PUSBPORT_DEVICE_EXTENSION FdoExtension;

    DPRINT("USBPORT_HandleSelectInterface: ... \n");

    FdoExtension = FdoDevice->DeviceExtension;

    KeWaitForSingleObject(&FdoExtension->DeviceSemaphore,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);

    ConfigurationHandle = Urb->UrbSelectInterface.ConfigurationHandle;

    Interface = &Urb->UrbSelectInterface.Interface;

    Length = Interface->Length + sizeof(USBD_PIPE_INFORMATION);
    Urb->UrbHeader.Length = Length;

    USBDStatus = USBPORT_InitInterfaceInfo(Interface, ConfigurationHandle);

    if (USBDStatus)
    {
        Interface->InterfaceHandle = (USBD_INTERFACE_HANDLE)-1;
        return USBPORT_USBDStatusToNtStatus(Urb, USBDStatus);
    }

    DeviceHandle = Urb->UrbHeader.UsbdDeviceHandle;

    InterfaceHandle = USBPORT_GetInterfaceHandle(ConfigurationHandle,
                                                 Interface->InterfaceNumber);

    if (InterfaceHandle)
    {
        RemoveEntryList(&InterfaceHandle->InterfaceLink);

        if (InterfaceHandle->InterfaceDescriptor.bNumEndpoints)
        {
            PipeHandle = &InterfaceHandle->PipeHandle[0];

            for (ix = 0;
                 ix < InterfaceHandle->InterfaceDescriptor.bNumEndpoints;
                 ix++)
            {
                USBPORT_ClosePipe(DeviceHandle, FdoDevice, PipeHandle);
                PipeHandle += 1;
            }
        }
    }

    iHandle = 0;

    USBDStatus = USBPORT_OpenInterface(Urb,
                                       DeviceHandle,
                                       FdoDevice,
                                       ConfigurationHandle,
                                       Interface,
                                       &iHandle,
                                       1);

    if (USBDStatus)
    {
        Interface->InterfaceHandle = (USBD_INTERFACE_HANDLE)-1;
    }
    else
    {
        if (InterfaceHandle)
            ExFreePoolWithTag(InterfaceHandle, USB_PORT_TAG);

        Interface->InterfaceHandle = iHandle;

        InsertTailList(&ConfigurationHandle->InterfaceHandleList,
                       &iHandle->InterfaceLink);
    }

    KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                       LOW_REALTIME_PRIORITY,
                       1,
                       FALSE);

    return USBPORT_USBDStatusToNtStatus(Urb, USBDStatus);
}

NTSTATUS
NTAPI
USBPORT_RemoveDevice(IN PDEVICE_OBJECT FdoDevice,
                     IN OUT PUSBPORT_DEVICE_HANDLE DeviceHandle,
                     IN ULONG Flags)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSB2_TT_EXTENSION TtExtension;
    ULONG ix;
    KIRQL OldIrql;

    DPRINT("USBPORT_RemoveDevice: DeviceHandle - %p, Flags - %x\n",
           DeviceHandle,
           Flags);

    FdoExtension = FdoDevice->DeviceExtension;

    if ((Flags & USBD_KEEP_DEVICE_DATA) ||
        (Flags & USBD_MARK_DEVICE_BUSY))
    {
        return STATUS_SUCCESS;
    }

    KeWaitForSingleObject(&FdoExtension->DeviceSemaphore,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);

    if (!USBPORT_ValidateDeviceHandle(FdoDevice, DeviceHandle))
    {
        KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                           LOW_REALTIME_PRIORITY,
                           1,
                           FALSE);

        DPRINT1("USBPORT_RemoveDevice: Not valid device handle\n");
        return STATUS_DEVICE_NOT_CONNECTED;
    }

    USBPORT_RemoveDeviceHandle(FdoDevice, DeviceHandle);

    DeviceHandle->Flags |= DEVICE_HANDLE_FLAG_REMOVED;

    USBPORT_AbortTransfers(FdoDevice, DeviceHandle);

    DPRINT("USBPORT_RemoveDevice: DeviceHandleLock - %x\n",
           DeviceHandle->DeviceHandleLock);

    while (InterlockedDecrement(&DeviceHandle->DeviceHandleLock) >= 0)
    {
        InterlockedIncrement(&DeviceHandle->DeviceHandleLock);
        USBPORT_Wait(FdoDevice, 100);
    }

    DPRINT("USBPORT_RemoveDevice: DeviceHandleLock ok\n");

    if (DeviceHandle->ConfigHandle)
    {
        USBPORT_CloseConfiguration(DeviceHandle, FdoDevice);
    }

    USBPORT_ClosePipe(DeviceHandle, FdoDevice, &DeviceHandle->PipeHandle);

    if (DeviceHandle->DeviceAddress)
    {
        USBPORT_FreeUsbAddress(FdoDevice, DeviceHandle->DeviceAddress);
    }

    if (!IsListEmpty(&DeviceHandle->TtList))
    {
        DPRINT1("USBPORT_RemoveDevice: DeviceHandle->TtList not empty\n");
    }

    while (!IsListEmpty(&DeviceHandle->TtList))
    {
        TtExtension = CONTAINING_RECORD(DeviceHandle->TtList.Flink,
                                        USB2_TT_EXTENSION,
                                        Link);

        RemoveHeadList(&DeviceHandle->TtList);

        DPRINT("USBPORT_RemoveDevice: TtExtension - %p\n", TtExtension);

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

        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;
            }

            DPRINT("USBPORT_RemoveDevice: ExFreePoolWithTag TtExtension - %p\n", TtExtension);
            ExFreePoolWithTag(TtExtension, USB_PORT_TAG);
        }

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

    KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                       LOW_REALTIME_PRIORITY,
                       1,
                       FALSE);

    if (!(DeviceHandle->Flags & DEVICE_HANDLE_FLAG_ROOTHUB))
    {
        ExFreePoolWithTag(DeviceHandle, USB_PORT_TAG);
    }

    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
USBPORT_RestoreDevice(IN PDEVICE_OBJECT FdoDevice,
                      IN OUT PUSBPORT_DEVICE_HANDLE OldDeviceHandle,
                      IN OUT PUSBPORT_DEVICE_HANDLE NewDeviceHandle)
{
    PUSBPORT_DEVICE_EXTENSION  FdoExtension;
    PLIST_ENTRY iHandleList;
    PUSBPORT_ENDPOINT Endpoint;
    USBPORT_ENDPOINT_REQUIREMENTS EndpointRequirements = {0};
    USB_DEFAULT_PIPE_SETUP_PACKET SetupPacket;
    NTSTATUS Status = STATUS_SUCCESS;
    USBD_STATUS USBDStatus;
    KIRQL OldIrql;
    PUSBPORT_INTERFACE_HANDLE InterfaceHandle;
    PUSBPORT_PIPE_HANDLE PipeHandle;
    PUSBPORT_REGISTRATION_PACKET Packet;

    DPRINT("USBPORT_RestoreDevice: OldDeviceHandle - %p, NewDeviceHandle - %p\n",
           OldDeviceHandle,
           NewDeviceHandle);

    FdoExtension = FdoDevice->DeviceExtension;

    KeWaitForSingleObject(&FdoExtension->DeviceSemaphore,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);

    if (!USBPORT_ValidateDeviceHandle(FdoDevice, OldDeviceHandle))
    {
        KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                           LOW_REALTIME_PRIORITY,
                           1,
                           FALSE);

#ifndef NDEBUG
        DPRINT("USBPORT_RestoreDevice: OldDeviceHandle not valid\n");
        DbgBreakPoint();
#endif
        return STATUS_DEVICE_NOT_CONNECTED;
    }

    if (!USBPORT_ValidateDeviceHandle(FdoDevice, NewDeviceHandle))
    {
        KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                           LOW_REALTIME_PRIORITY,
                           1,
                           FALSE);
#ifndef NDEBUG
        DPRINT("USBPORT_RestoreDevice: NewDeviceHandle not valid\n");
        DbgBreakPoint();
#endif
        return STATUS_DEVICE_NOT_CONNECTED;
    }

    USBPORT_RemoveDeviceHandle(FdoDevice, OldDeviceHandle);
    USBPORT_AbortTransfers(FdoDevice, OldDeviceHandle);

    while (InterlockedDecrement(&OldDeviceHandle->DeviceHandleLock) >= 0)
    {
        InterlockedIncrement(&OldDeviceHandle->DeviceHandleLock);
        USBPORT_Wait(FdoDevice, 100);
    }

    if (sizeof(USB_DEVICE_DESCRIPTOR) == RtlCompareMemory(&NewDeviceHandle->DeviceDescriptor,
                                                          &OldDeviceHandle->DeviceDescriptor,
                                                          sizeof(USB_DEVICE_DESCRIPTOR)))
    {
        NewDeviceHandle->ConfigHandle = OldDeviceHandle->ConfigHandle;

        if (OldDeviceHandle->ConfigHandle)
        {
            RtlZeroMemory(&SetupPacket, sizeof(USB_DEFAULT_PIPE_SETUP_PACKET));

            SetupPacket.bmRequestType.Dir = BMREQUEST_HOST_TO_DEVICE;
            SetupPacket.bRequest = USB_REQUEST_SET_CONFIGURATION;
            SetupPacket.wValue.W = OldDeviceHandle->ConfigHandle->ConfigurationDescriptor->bConfigurationValue;
            SetupPacket.wIndex.W = 0;
            SetupPacket.wLength = 0;

            USBPORT_SendSetupPacket(NewDeviceHandle,
                                    FdoDevice,
                                    &SetupPacket,
                                    NULL,
                                    0,
                                    NULL,
                                    &USBDStatus);

            if (USBD_ERROR(USBDStatus))
                Status = USBPORT_USBDStatusToNtStatus(NULL, USBDStatus);

            if (NT_SUCCESS(Status))
            {
                iHandleList = NewDeviceHandle->ConfigHandle->InterfaceHandleList.Flink;

                while (iHandleList &&
                       iHandleList != &NewDeviceHandle->ConfigHandle->InterfaceHandleList)
                {
                    InterfaceHandle = CONTAINING_RECORD(iHandleList,
                                                        USBPORT_INTERFACE_HANDLE,
                                                        InterfaceLink);

                    if (InterfaceHandle->AlternateSetting)
                    {
                        RtlZeroMemory(&SetupPacket, sizeof(USB_DEFAULT_PIPE_SETUP_PACKET));

                        SetupPacket.bmRequestType.Dir = BMREQUEST_HOST_TO_DEVICE;
                        SetupPacket.bmRequestType.Type = BMREQUEST_STANDARD;
                        SetupPacket.bmRequestType.Recipient = BMREQUEST_TO_INTERFACE;

                        SetupPacket.bRequest = USB_REQUEST_SET_INTERFACE;
                        SetupPacket.wValue.W = InterfaceHandle->InterfaceDescriptor.bAlternateSetting;
                        SetupPacket.wIndex.W = InterfaceHandle->InterfaceDescriptor.bInterfaceNumber;
                        SetupPacket.wLength = 0;

                        USBPORT_SendSetupPacket(NewDeviceHandle,
                                                FdoDevice,
                                                &SetupPacket,
                                                NULL,
                                                0,
                                                NULL,
                                                &USBDStatus);
                    }

                    iHandleList = iHandleList->Flink;
                }
            }
        }

        if (NewDeviceHandle->Flags & DEVICE_HANDLE_FLAG_USB2HUB)
        {
            DPRINT1("USBPORT_RestoreDevice: FIXME Transaction Translator\n");
            NewDeviceHandle->TtCount = OldDeviceHandle->TtCount;

#ifndef NDEBUG
            DbgBreakPoint();
#endif
        }

        while (!IsListEmpty(&OldDeviceHandle->PipeHandleList))
        {
            PipeHandle = CONTAINING_RECORD(OldDeviceHandle->PipeHandleList.Flink,
                                           USBPORT_PIPE_HANDLE,
                                           PipeLink);

            DPRINT("USBPORT_RestoreDevice: PipeHandle - %p\n", PipeHandle);

            USBPORT_RemovePipeHandle(OldDeviceHandle, PipeHandle);

            if (PipeHandle != &OldDeviceHandle->PipeHandle)
            {
                USBPORT_AddPipeHandle(NewDeviceHandle, PipeHandle);

                if (!(PipeHandle->Flags & PIPE_HANDLE_FLAG_NULL_PACKET_SIZE))
                {
                    Endpoint = PipeHandle->Endpoint;
                    Endpoint->DeviceHandle = NewDeviceHandle;
                    Endpoint->EndpointProperties.DeviceAddress = NewDeviceHandle->DeviceAddress;

                    Packet = &FdoExtension->MiniPortInterface->Packet;

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

                        Packet->ReopenEndpoint(FdoExtension->MiniPortExt,
                                               &Endpoint->EndpointProperties,
                                               Endpoint + 1);

                        Packet->SetEndpointDataToggle(FdoExtension->MiniPortExt,
                                                      Endpoint + 1,
                                                      0);

                        Packet->SetEndpointStatus(FdoExtension->MiniPortExt,
                                                  Endpoint + 1,
                                                  USBPORT_ENDPOINT_RUN);

                        KeReleaseSpinLock(&FdoExtension->MiniportSpinLock,
                                          OldIrql);
                    }
                    else
                    {
                        MiniportCloseEndpoint(FdoDevice, Endpoint);

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

                        RtlZeroMemory((PVOID)Endpoint->EndpointProperties.BufferVA,
                                      Endpoint->EndpointProperties.BufferLength);

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

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

                        KeReleaseSpinLock(&FdoExtension->MiniportSpinLock,
                                          OldIrql);

                        MiniportOpenEndpoint(FdoDevice, Endpoint);

                        Endpoint->Flags &= ~(ENDPOINT_FLAG_NUKE |
                                             ENDPOINT_FLAG_ABORTING);

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

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

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

                            KeReleaseSpinLockFromDpcLevel(&FdoExtension->MiniportSpinLock);
                        }

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

        USBPORT_AddPipeHandle(OldDeviceHandle, &OldDeviceHandle->PipeHandle);
    }
    else
    {
#ifndef NDEBUG
        DPRINT("USBPORT_RestoreDevice: New DeviceDescriptor != Old DeviceDescriptor\n");
        DbgBreakPoint();
#endif
        Status = STATUS_UNSUCCESSFUL;
    }

    USBPORT_ClosePipe(OldDeviceHandle, FdoDevice, &OldDeviceHandle->PipeHandle);

    if (OldDeviceHandle->DeviceAddress != 0)
        USBPORT_FreeUsbAddress(FdoDevice, OldDeviceHandle->DeviceAddress);

    KeReleaseSemaphore(&FdoExtension->DeviceSemaphore,
                       LOW_REALTIME_PRIORITY,
                       1,
                       FALSE);

    ExFreePoolWithTag(OldDeviceHandle, USB_PORT_TAG);

    return Status;
}

NTSTATUS
NTAPI
USBPORT_InitializeTT(IN PDEVICE_OBJECT FdoDevice,
                     IN PUSBPORT_DEVICE_HANDLE HubDeviceHandle,
                     IN ULONG TtNumber)
{
    PUSBPORT_DEVICE_EXTENSION FdoExtension;
    PUSB2_TT_EXTENSION TtExtension;
    ULONG ix;

    DPRINT("USBPORT_InitializeTT: HubDeviceHandle - %p, TtNumber - %X\n",
           HubDeviceHandle,
           TtNumber);

    FdoExtension = FdoDevice->DeviceExtension;

    TtExtension = ExAllocatePoolWithTag(NonPagedPool,
                                        sizeof(USB2_TT_EXTENSION),
                                        USB_PORT_TAG);

    if (!TtExtension)
    {
        DPRINT1("USBPORT_InitializeTT: ExAllocatePoolWithTag return NULL\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    DPRINT("USBPORT_InitializeTT: TtExtension - %p\n", TtExtension);

    RtlZeroMemory(TtExtension, sizeof(USB2_TT_EXTENSION));

    TtExtension->DeviceAddress = HubDeviceHandle->DeviceAddress;
    TtExtension->TtNumber = TtNumber;
    TtExtension->RootHubPdo = FdoExtension->RootHubPdo;
    TtExtension->BusBandwidth = TOTAL_USB11_BUS_BANDWIDTH;

    InitializeListHead(&TtExtension->EndpointList);

    /* 90% maximum allowed for periodic endpoints */
    for (ix = 0; ix < USB2_FRAMES; ix++)
    {
        TtExtension->Bandwidth[ix] = TtExtension->BusBandwidth -
                                     TtExtension->BusBandwidth / 10;
    }

    USBPORT_UpdateAllocatedBwTt(TtExtension);

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

    USB2_InitTT(FdoExtension->Usb2Extension, &TtExtension->Tt);

    InsertTailList(&HubDeviceHandle->TtList, &TtExtension->Link);

    return STATUS_SUCCESS;
}

NTSTATUS
NTAPI
USBPORT_Initialize20Hub(IN PDEVICE_OBJECT FdoDevice,
                        IN PUSBPORT_DEVICE_HANDLE HubDeviceHandle,
                        IN ULONG TtCount)
{
    NTSTATUS Status;
    ULONG ix;

    DPRINT("USBPORT_Initialize20Hub: TtCount - %X\n", TtCount);

    if (!HubDeviceHandle)
    {
        return STATUS_INVALID_PARAMETER;
    }

    if (HubDeviceHandle->Flags & DEVICE_HANDLE_FLAG_ROOTHUB)
    {
        return STATUS_SUCCESS;
    }

    if (TtCount == 0)
    {
        HubDeviceHandle->TtCount = 0;
        return STATUS_SUCCESS;
    }

    for (ix = 0; ix < TtCount; ++ix)
    {
        Status = USBPORT_InitializeTT(FdoDevice, HubDeviceHandle, ix + 1);

        if (!NT_SUCCESS(Status))
            break;
    }

    HubDeviceHandle->TtCount = TtCount;

    return Status;
}