/*
 * PROJECT:         ReactOS Universal Serial Bus Hub Driver
 * LICENSE:         GPL - See COPYING in the top level directory
 * FILE:            drivers/usb/usbhub/fdo.c
 * PURPOSE:         Handle FDO
 * PROGRAMMERS:
 *                  Michael Martin (michael.martin@reactos.org)
 *                  Johannes Anderwald (johannes.anderwald@reactos.org)
 */

#include "usbhub.h"

#include <stdio.h>

#define NDEBUG
#include <debug.h>

NTSTATUS
QueryStatusChangeEndpoint(
    IN PDEVICE_OBJECT DeviceObject);

NTSTATUS
CreateUsbChildDeviceObject(
    IN PDEVICE_OBJECT UsbHubDeviceObject,
    IN LONG PortId,
    OUT PDEVICE_OBJECT *UsbChildDeviceObject,
    IN ULONG PortStatus);

NTSTATUS
DestroyUsbChildDeviceObject(
    IN PDEVICE_OBJECT UsbHubDeviceObject,
    IN LONG PortId);


NTSTATUS
GetPortStatusAndChange(
    IN PDEVICE_OBJECT RootHubDeviceObject,
    IN ULONG PortId,
    OUT PPORT_STATUS_CHANGE StatusChange)
{
    NTSTATUS Status;
    PURB Urb;

    //
    // Allocate URB
    //
    Urb = ExAllocatePoolWithTag(NonPagedPool, sizeof(URB), USB_HUB_TAG);
    if (!Urb)
    {
        DPRINT1("Failed to allocate memory for URB!\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Zero it
    //
    RtlZeroMemory(Urb, sizeof(URB));

    //
    // Initialize URB for getting Port Status
    //
    UsbBuildVendorRequest(Urb,
                          URB_FUNCTION_CLASS_OTHER,
                          sizeof(Urb->UrbControlVendorClassRequest),
                          USBD_TRANSFER_DIRECTION_OUT,
                          0,
                          USB_REQUEST_GET_STATUS,
                          0,
                          PortId,
                          StatusChange,
                          0,
                          sizeof(PORT_STATUS_CHANGE),
                          0);

    // FIXME: support usb hubs
    Urb->UrbHeader.UsbdDeviceHandle = NULL;


    //
    // Query the Root Hub
    //
    Status = SubmitRequestToRootHub(RootHubDeviceObject, IOCTL_INTERNAL_USB_SUBMIT_URB, Urb, NULL);

    //
    // Free URB
    //
    ExFreePool(Urb);

    return Status;
}

NTSTATUS
SetPortFeature(
    IN PDEVICE_OBJECT RootHubDeviceObject,
    IN ULONG PortId,
    IN ULONG Feature)
{
    NTSTATUS Status;
    PURB Urb;

    //
    // Allocate URB
    //
    Urb = ExAllocatePoolWithTag(NonPagedPool, sizeof(URB), USB_HUB_TAG);
    if (!Urb)
    {
        DPRINT1("Failed to allocate memory for URB!\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Zero it
    //
    RtlZeroMemory(Urb, sizeof(URB));

    //
    // Initialize URB for Clearing Port Reset
    //
    UsbBuildVendorRequest(Urb,
                          URB_FUNCTION_CLASS_OTHER,
                          sizeof(Urb->UrbControlVendorClassRequest),
                          USBD_TRANSFER_DIRECTION_IN,
                          0,
                          USB_REQUEST_SET_FEATURE,
                          Feature,
                          PortId,
                          NULL,
                          0,
                          0,
                          0);

    // FIXME support usbhubs
    Urb->UrbHeader.UsbdDeviceHandle = NULL;

    //
    // Query the Root Hub
    //
    Status = SubmitRequestToRootHub(RootHubDeviceObject, IOCTL_INTERNAL_USB_SUBMIT_URB, Urb, NULL);

    //
    // Free URB
    //
    ExFreePool(Urb);

    return Status;
}

NTSTATUS
ClearPortFeature(
    IN PDEVICE_OBJECT RootHubDeviceObject,
    IN ULONG PortId,
    IN ULONG Feature)
{
    NTSTATUS Status;
    PURB Urb;

    //
    // Allocate a URB
    //
    Urb = ExAllocatePoolWithTag(NonPagedPool, sizeof(URB), USB_HUB_TAG);
    if (!Urb)
    {
        DPRINT1("Failed to allocate memory for URB!\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Zero it
    //
    RtlZeroMemory(Urb, sizeof(URB));

    //
    // Initialize URB for Clearing Port Reset
    //
    UsbBuildVendorRequest(Urb,
                          URB_FUNCTION_CLASS_OTHER,
                          sizeof(Urb->UrbControlVendorClassRequest),
                          USBD_TRANSFER_DIRECTION_IN,
                          0,
                          USB_REQUEST_CLEAR_FEATURE,
                          Feature,
                          PortId,
                          NULL,
                          0,
                          0,
                          0);

    // FIXME: support usb hubs
    Urb->UrbHeader.UsbdDeviceHandle = NULL;

    //
    // Query the Root Hub
    //
    Status = SubmitRequestToRootHub(RootHubDeviceObject, IOCTL_INTERNAL_USB_SUBMIT_URB, Urb, NULL);

    //
    // Free URB
    //
    ExFreePool(Urb);

    return Status;
}

VOID NTAPI
DeviceStatusChangeThread(
    IN PVOID Context)
{
    NTSTATUS Status;
    PDEVICE_OBJECT DeviceObject, RootHubDeviceObject;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;
    PWORK_ITEM_DATA WorkItemData;
    PORT_STATUS_CHANGE PortStatus;
    ULONG PortId;
    BOOLEAN SignalResetComplete = FALSE;

    DPRINT("Entered DeviceStatusChangeThread, Context %x\n", Context);

    WorkItemData = (PWORK_ITEM_DATA)Context;
    DeviceObject = (PDEVICE_OBJECT)WorkItemData->Context;
    HubDeviceExtension = (PHUB_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    RootHubDeviceObject = HubDeviceExtension->RootHubPhysicalDeviceObject;
    //
    // Loop all ports
    //
    for (PortId = 1; PortId <= HubDeviceExtension->UsbExtHubInfo.NumberOfPorts; PortId++)
    {
        //
        // Get Port Status
        //
        Status = GetPortStatusAndChange(RootHubDeviceObject, PortId, &PortStatus);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("Failed to get port status for port %d, Status %x\n", PortId, Status);
            // FIXME: Do we really want to halt further SCE requests?
            return;
        }

        DPRINT("Port %d Status %x\n", PortId, PortStatus.Status);
        DPRINT("Port %d Change %x\n", PortId, PortStatus.Change);


        //
        // Check for new device connection
        //
        if (PortStatus.Change & USB_PORT_STATUS_CONNECT)
        {
            //
            // Clear Port Connect
            //
            Status = ClearPortFeature(RootHubDeviceObject, PortId, C_PORT_CONNECTION);
            if (!NT_SUCCESS(Status))
            {
                DPRINT1("Failed to clear connection change for port %d\n", PortId);
                continue;
            }

            //
            // Is this a connect or disconnect?
            //
            if (!(PortStatus.Status & USB_PORT_STATUS_CONNECT))
            {
                DPRINT1("Device disconnected from port %d\n", PortId);

                Status = DestroyUsbChildDeviceObject(DeviceObject, PortId);
                if (!NT_SUCCESS(Status))
                {
                    DPRINT1("Failed to delete child device object after disconnect\n");
                    continue;
                }
            }
            else
            {
                DPRINT1("Device connected from port %d\n", PortId);

                // No SCE completion done for clearing C_PORT_CONNECT

                //
                // Reset Port
                //
                Status = SetPortFeature(RootHubDeviceObject, PortId, PORT_RESET);
                if (!NT_SUCCESS(Status))
                {
                    DPRINT1("Failed to reset port %d\n", PortId);
                    SignalResetComplete = TRUE;
                    continue;
                }
            }
        }
        else if (PortStatus.Change & USB_PORT_STATUS_ENABLE)
        {
            //
            // Clear Enable
            //
            Status = ClearPortFeature(RootHubDeviceObject, PortId, C_PORT_ENABLE);
            if (!NT_SUCCESS(Status))
            {
                DPRINT1("Failed to clear enable change on port %d\n", PortId);
                continue;
            }
        }
        else if (PortStatus.Change & USB_PORT_STATUS_RESET)
        {
            //
            // Request event signalling later
            //
            SignalResetComplete = TRUE;

            //
            // Clear Reset
            //
            Status = ClearPortFeature(RootHubDeviceObject, PortId, C_PORT_RESET);
            if (!NT_SUCCESS(Status))
            {
                DPRINT1("Failed to clear reset change on port %d\n", PortId);
                continue;
            }

            //
            // Get Port Status
            //
            Status = GetPortStatusAndChange(RootHubDeviceObject, PortId, &PortStatus);
            if (!NT_SUCCESS(Status))
            {
                DPRINT1("Failed to get port status for port %d, Status %x\n", PortId, Status);
                // FIXME: Do we really want to halt further SCE requests?
                return;
            }

            DPRINT("Port %d Status %x\n", PortId, PortStatus.Status);
            DPRINT("Port %d Change %x\n", PortId, PortStatus.Change);

            //
            // Check that reset was cleared
            //
            if(PortStatus.Change & USB_PORT_STATUS_RESET)
            {
                DPRINT1("Port did not clear reset! Possible Hardware problem!\n");
                continue;
            }

            //
            // Check if the device is still connected
            //
            if (!(PortStatus.Status & USB_PORT_STATUS_CONNECT))
            {
                DPRINT1("Device has been disconnected\n");
                continue;
            }

            //
            // Make sure its Connected and Enabled
            //
            if (!(PortStatus.Status & (USB_PORT_STATUS_CONNECT | USB_PORT_STATUS_ENABLE)))
            {
                DPRINT1("Usb Device is not connected and enabled!\n");
                //
                // Attempt another reset
                //
                Status = SetPortFeature(RootHubDeviceObject, PortId, PORT_RESET);
                if (!NT_SUCCESS(Status))
                {
                    DPRINT1("Failed to reset port %d\n", PortId);
                }
                continue;
            }

            //
            // This is a new device
            //
            Status = CreateUsbChildDeviceObject(DeviceObject, PortId, NULL, PortStatus.Status);
        }
    }

    ExFreePool(WorkItemData);

    //
    // Send another SCE Request
    //
    DPRINT("Sending another SCE!\n");
    QueryStatusChangeEndpoint(DeviceObject);

    //
    // Check if a reset event was satisfied
    //
    if (SignalResetComplete)
    {
        //
        // Signal anyone waiting on it
        //
        KeSetEvent(&HubDeviceExtension->ResetComplete, IO_NO_INCREMENT, FALSE);
    }
}

NTSTATUS
NTAPI
StatusChangeEndpointCompletion(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context)
{
    PDEVICE_OBJECT RealDeviceObject;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;
    PWORK_ITEM_DATA WorkItemData;

    RealDeviceObject = (PDEVICE_OBJECT)Context;
    HubDeviceExtension = (PHUB_DEVICE_EXTENSION)RealDeviceObject->DeviceExtension;

    //
    // NOTE: USBPORT frees this IRP
    //
    DPRINT("Received Irp %x, HubDeviceExtension->PendingSCEIrp %x\n", Irp, HubDeviceExtension->PendingSCEIrp);
    //IoFreeIrp(Irp);

    //
    // Create and initialize work item data
    //
    WorkItemData = ExAllocatePoolWithTag(NonPagedPool, sizeof(WORK_ITEM_DATA), USB_HUB_TAG);
    if (!WorkItemData)
    {
        DPRINT1("Failed to allocate memory!n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }
    WorkItemData->Context = RealDeviceObject;

    DPRINT("Queuing work item\n");

    //
    // Queue the work item to handle initializing the device
    //
    ExInitializeWorkItem(&WorkItemData->WorkItem, DeviceStatusChangeThread, (PVOID)WorkItemData);
    ExQueueWorkItem(&WorkItemData->WorkItem, DelayedWorkQueue);

    //
    // Return more processing required so the IO Manger doesn’t try to mess with IRP just freed
    //
    return STATUS_MORE_PROCESSING_REQUIRED;
}

NTSTATUS
QueryStatusChangeEndpoint(
    IN PDEVICE_OBJECT DeviceObject)
{
    PDEVICE_OBJECT RootHubDeviceObject;
    PIO_STACK_LOCATION Stack;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;
    PURB PendingSCEUrb;

    HubDeviceExtension = (PHUB_DEVICE_EXTENSION)DeviceObject->DeviceExtension;
    RootHubDeviceObject = HubDeviceExtension->RootHubPhysicalDeviceObject;

    //
    // Allocate a URB
    //
    PendingSCEUrb = ExAllocatePoolWithTag(NonPagedPool, sizeof(URB), USB_HUB_TAG);

    //
    // Initialize URB for Status Change Endpoint request
    //
    UsbBuildInterruptOrBulkTransferRequest(PendingSCEUrb,
                                           sizeof(struct _URB_BULK_OR_INTERRUPT_TRANSFER),
                                           HubDeviceExtension->PipeHandle,
                                           HubDeviceExtension->PortStatusChange,
                                           NULL,
                                           sizeof(USHORT) * 2 * HubDeviceExtension->UsbExtHubInfo.NumberOfPorts,
                                           USBD_TRANSFER_DIRECTION_IN | USBD_SHORT_TRANSFER_OK,
                                           NULL);

    // Set the device handle
    PendingSCEUrb->UrbHeader.UsbdDeviceHandle = HubDeviceExtension->RootHubHandle;

    //
    // Allocate an Irp
    //
    HubDeviceExtension->PendingSCEIrp = ExAllocatePoolWithTag(NonPagedPool,
                                                              IoSizeOfIrp(RootHubDeviceObject->StackSize),
                                                              USB_HUB_TAG);
/*
    HubDeviceExtension->PendingSCEIrp = IoAllocateIrp(RootHubDeviceObject->StackSize,
                                  FALSE);
*/
    DPRINT("Allocated IRP %x\n", HubDeviceExtension->PendingSCEIrp);

    if (!HubDeviceExtension->PendingSCEIrp)
    {
        DPRINT1("USBHUB: Failed to allocate IRP for SCE request!\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Initialize the IRP
    //
    IoInitializeIrp(HubDeviceExtension->PendingSCEIrp,
                    IoSizeOfIrp(RootHubDeviceObject->StackSize),
                    RootHubDeviceObject->StackSize);

    HubDeviceExtension->PendingSCEIrp->IoStatus.Status = STATUS_NOT_SUPPORTED;
    HubDeviceExtension->PendingSCEIrp->IoStatus.Information = 0;
    HubDeviceExtension->PendingSCEIrp->Flags = 0;
    HubDeviceExtension->PendingSCEIrp->UserBuffer = NULL;

    //
    // Get the Next Stack Location and Initialize it
    //
    Stack = IoGetNextIrpStackLocation(HubDeviceExtension->PendingSCEIrp);
    Stack->DeviceObject = DeviceObject;
    Stack->Parameters.Others.Argument1 = PendingSCEUrb;
    Stack->Parameters.Others.Argument2 = NULL;
    Stack->MajorFunction =  IRP_MJ_INTERNAL_DEVICE_CONTROL;
    Stack->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB;

    //
    // Set the completion routine for when device is connected to root hub
    //
    IoSetCompletionRoutine(HubDeviceExtension->PendingSCEIrp,
                           StatusChangeEndpointCompletion,
                           DeviceObject,
                           TRUE,
                           TRUE,
                           TRUE);

    //
    // Send to RootHub
    //
    DPRINT("DeviceObject is %x\n", DeviceObject);
    DPRINT("Iocalldriver %x with irp %x\n", RootHubDeviceObject, HubDeviceExtension->PendingSCEIrp);
    IoCallDriver(RootHubDeviceObject, HubDeviceExtension->PendingSCEIrp);

    return STATUS_PENDING;
}

NTSTATUS
QueryInterface(
    IN PDEVICE_OBJECT DeviceObject,
    IN CONST GUID InterfaceType,
    IN LONG Size,
    IN LONG Version,
    OUT PVOID Interface)
{
    KEVENT Event;
    PIRP Irp;
    IO_STATUS_BLOCK IoStatus;
    NTSTATUS Status;
    PIO_STACK_LOCATION Stack = NULL;

    //
    // Initialize the Event used to wait for Irp completion
    //
    KeInitializeEvent(&Event, NotificationEvent, FALSE);

    //
    // Build Control Request
    //
    Irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP,
                                       DeviceObject,
                                       NULL,
                                       0,
                                       NULL,
                                       &Event,
                                       &IoStatus);

    //
    // Get Next Stack Location and Initialize it.
    //
    Stack = IoGetNextIrpStackLocation(Irp);
    Stack->MinorFunction = IRP_MN_QUERY_INTERFACE;
    Stack->Parameters.QueryInterface.InterfaceType= &InterfaceType;//USB_BUS_INTERFACE_HUB_GUID;
    Stack->Parameters.QueryInterface.Size = Size;
    Stack->Parameters.QueryInterface.Version = Version;
    Stack->Parameters.QueryInterface.Interface = Interface;
    Stack->Parameters.QueryInterface.InterfaceSpecificData = NULL;

    Status = IoCallDriver(DeviceObject, Irp);

    if (Status == STATUS_PENDING)
    {
        DPRINT("Operation pending\n");
        KeWaitForSingleObject(&Event, Suspended, KernelMode, FALSE, NULL);
        Status = IoStatus.Status;
    }

    return Status;
}

NTSTATUS
GetUsbDeviceDescriptor(
    IN PDEVICE_OBJECT ChildDeviceObject,
    IN UCHAR DescriptorType,
    IN UCHAR Index,
    IN USHORT LangId,
    OUT PVOID TransferBuffer,
    IN ULONG TransferBufferLength)
{
    NTSTATUS Status;
    PDEVICE_OBJECT RootHubDeviceObject;
    PURB Urb;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;
    PHUB_CHILDDEVICE_EXTENSION ChildDeviceExtension;

    //
    // Get the Hubs Device Extension
    //
    ChildDeviceExtension = (PHUB_CHILDDEVICE_EXTENSION)ChildDeviceObject->DeviceExtension;
    HubDeviceExtension = (PHUB_DEVICE_EXTENSION) ChildDeviceExtension->ParentDeviceObject->DeviceExtension;
    RootHubDeviceObject = HubDeviceExtension->RootHubPhysicalDeviceObject;

    //
    // Allocate a URB
    //
    Urb = ExAllocatePoolWithTag(NonPagedPool, sizeof(URB), USB_HUB_TAG);
    if (!Urb)
    {
        DPRINT1("Failed to allocate memory for URB!\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Zero it
    //
    RtlZeroMemory(Urb, sizeof(URB));

    //
    // Initialize URB for getting device descriptor
    //
    UsbBuildGetDescriptorRequest(Urb,
                                 sizeof(Urb->UrbControlDescriptorRequest),
                                 DescriptorType,
                                 Index,
                                 LangId,
                                 TransferBuffer,
                                 NULL,
                                 TransferBufferLength,
                                 NULL);

    //
    // Set the device handle
    //
    Urb->UrbHeader.UsbdDeviceHandle = (PVOID)ChildDeviceExtension->UsbDeviceHandle;

    //
    // Query the Root Hub
    //
    Status = SubmitRequestToRootHub(RootHubDeviceObject,
                                    IOCTL_INTERNAL_USB_SUBMIT_URB,
                                    Urb,
                                    NULL);

    return Status;
}

NTSTATUS
GetUsbStringDescriptor(
    IN PDEVICE_OBJECT ChildDeviceObject,
    IN UCHAR Index,
    IN USHORT LangId,
    OUT PVOID *TransferBuffer,
    OUT USHORT *Size)
{
    NTSTATUS Status;
    PUSB_STRING_DESCRIPTOR StringDesc = NULL;
    ULONG SizeNeeded;
    LPWSTR Buffer;

    StringDesc = ExAllocatePoolWithTag(NonPagedPool,
                                       sizeof(USB_STRING_DESCRIPTOR),
                                       USB_HUB_TAG);
    if (!StringDesc)
    {
        DPRINT1("Failed to allocate buffer for string!\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Get the index string descriptor length
    // FIXME: Implement LangIds
    //
    Status = GetUsbDeviceDescriptor(ChildDeviceObject,
                                    USB_STRING_DESCRIPTOR_TYPE,
                                    Index,
                                    0x0409,
                                    StringDesc,
                                    sizeof(USB_STRING_DESCRIPTOR));
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("GetUsbDeviceDescriptor failed with status %x\n", Status);
        ExFreePool(StringDesc);
        return Status;
    }
    DPRINT1("StringDesc->bLength %d\n", StringDesc->bLength);

    //
    // Did we get something more than the length of the first two fields of structure?
    //
    if (StringDesc->bLength == 2)
    {
        DPRINT1("USB Device Error!\n");
        ExFreePool(StringDesc);
        return STATUS_DEVICE_DATA_ERROR;
    }
    SizeNeeded = StringDesc->bLength + sizeof(WCHAR);

    //
    // Free String
    //
    ExFreePool(StringDesc);

    //
    // Recreate with appropriate size
    //
    StringDesc = ExAllocatePoolWithTag(NonPagedPool,
                                       SizeNeeded,
                                       USB_HUB_TAG);
    if (!StringDesc)
    {
        DPRINT1("Failed to allocate buffer for string!\n");
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    RtlZeroMemory(StringDesc, SizeNeeded);

    //
    // Get the string
    //
    Status = GetUsbDeviceDescriptor(ChildDeviceObject,
                                    USB_STRING_DESCRIPTOR_TYPE,
                                    Index,
                                    0x0409,
                                    StringDesc,
                                    SizeNeeded);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("GetUsbDeviceDescriptor failed with status %x\n", Status);
        ExFreePool(StringDesc);
        return Status;
    }

    //
    // Allocate Buffer to return
    //
    Buffer = ExAllocatePoolWithTag(NonPagedPool,
                                   SizeNeeded,
                                   USB_HUB_TAG);
    if (!Buffer)
    {
        DPRINT1("Failed to allocate buffer for string!\n");
        ExFreePool(StringDesc);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    RtlZeroMemory(Buffer, SizeNeeded);

    //
    // Copy the string to destination
    //
    RtlCopyMemory(Buffer, StringDesc->bString, SizeNeeded - FIELD_OFFSET(USB_STRING_DESCRIPTOR, bString));
    *Size = SizeNeeded;
    *TransferBuffer = Buffer;

    ExFreePool(StringDesc);

    return STATUS_SUCCESS;
}

ULONG
IsCompositeDevice(
    IN PUSB_DEVICE_DESCRIPTOR DeviceDescriptor,
    IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor)
{
    if (DeviceDescriptor->bNumConfigurations != 1)
    {
         //
         // composite device must have only one configuration
         //
         DPRINT1("IsCompositeDevice bNumConfigurations %x\n", DeviceDescriptor->bNumConfigurations);
         return FALSE;
    }

    if (ConfigurationDescriptor->bNumInterfaces < 2)
    {
        //
        // composite device must have multiple interfaces
        //
        DPRINT1("IsCompositeDevice bNumInterfaces %x\n", ConfigurationDescriptor->bNumInterfaces);
        return FALSE;
    }

    if (DeviceDescriptor->bDeviceClass == 0)
    {
        //
        // composite device
        //
        ASSERT(DeviceDescriptor->bDeviceSubClass == 0);
        ASSERT(DeviceDescriptor->bDeviceProtocol == 0);
        DPRINT1("IsCompositeDevice: TRUE\n");
        return TRUE;
    }

    if (DeviceDescriptor->bDeviceClass == 0xEF &&
        DeviceDescriptor->bDeviceSubClass == 0x02 &&
        DeviceDescriptor->bDeviceProtocol == 0x01)
    {
        //
        // USB-IF association descriptor
        //
        DPRINT1("IsCompositeDevice: TRUE\n");
        return TRUE;
    }

    DPRINT1("DeviceDescriptor bDeviceClass %x bDeviceSubClass %x bDeviceProtocol %x\n", DeviceDescriptor->bDeviceClass, DeviceDescriptor->bDeviceSubClass, DeviceDescriptor->bDeviceProtocol);

    //
    // not a composite device
    //
    return FALSE;
}

NTSTATUS
CreateDeviceIds(
    PDEVICE_OBJECT UsbChildDeviceObject)
{
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG Index = 0;
    LPWSTR DeviceString;
    WCHAR Buffer[200];
    PHUB_CHILDDEVICE_EXTENSION UsbChildExtension;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;
    PUSB_DEVICE_DESCRIPTOR DeviceDescriptor;
    PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor;
    PUSB_INTERFACE_DESCRIPTOR InterfaceDescriptor;

    //
    // get child device extension
    //
    UsbChildExtension = (PHUB_CHILDDEVICE_EXTENSION)UsbChildDeviceObject->DeviceExtension;

    // get hub device extension
    HubDeviceExtension = (PHUB_DEVICE_EXTENSION) UsbChildExtension->ParentDeviceObject->DeviceExtension;

    //
    // get device descriptor
    //
    DeviceDescriptor = &UsbChildExtension->DeviceDesc;

    //
    // get configuration descriptor
    //
    ConfigurationDescriptor = UsbChildExtension->FullConfigDesc;

    //
    // use first interface descriptor available
    //
    InterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(ConfigurationDescriptor, ConfigurationDescriptor, 0, -1, -1, -1, -1);
    if (InterfaceDescriptor == NULL)
    {
         DPRINT1("Error USBD_ParseConfigurationDescriptorEx failed to parse interface descriptor\n");
         return STATUS_INVALID_PARAMETER;
    }

    ASSERT(InterfaceDescriptor);

    //
    // Construct the CompatibleIds
    //
    if (IsCompositeDevice(DeviceDescriptor, ConfigurationDescriptor))
    {
        //
        // sanity checks
        //
        ASSERT(DeviceDescriptor->bNumConfigurations == 1);
        ASSERT(ConfigurationDescriptor->bNumInterfaces > 1);
        Index += swprintf(&Buffer[Index],
                          L"USB\\DevClass_%02x&SubClass_%02x&Prot_%02x",
                          DeviceDescriptor->bDeviceClass, DeviceDescriptor->bDeviceSubClass, DeviceDescriptor->bDeviceProtocol) + 1;
        Index += swprintf(&Buffer[Index],
                          L"USB\\DevClass_%02x&SubClass_%02x",
                          DeviceDescriptor->bDeviceClass, DeviceDescriptor->bDeviceSubClass) + 1;
        Index += swprintf(&Buffer[Index],
                          L"USB\\DevClass_%02x",
                          DeviceDescriptor->bDeviceClass) + 1;
        Index += swprintf(&Buffer[Index],
                          L"USB\\COMPOSITE") + 1;
    }
    else
    {
        //
        // FIXME: support multiple configurations
        //
        ASSERT(DeviceDescriptor->bNumConfigurations == 1);

        if (DeviceDescriptor->bDeviceClass == 0)
        {
            Index += swprintf(&Buffer[Index],
                          L"USB\\Class_%02x&SubClass_%02x&Prot_%02x",
                          InterfaceDescriptor->bInterfaceClass, InterfaceDescriptor->bInterfaceSubClass, InterfaceDescriptor->bInterfaceProtocol) + 1;
            Index += swprintf(&Buffer[Index],
                          L"USB\\Class_%02x&SubClass_%02x",
                          InterfaceDescriptor->bInterfaceClass, InterfaceDescriptor->bInterfaceSubClass) + 1;
            Index += swprintf(&Buffer[Index],
                          L"USB\\Class_%02x",
                          InterfaceDescriptor->bInterfaceClass) + 1;
        }
        else
        {
            Index += swprintf(&Buffer[Index],
                          L"USB\\Class_%02x&SubClass_%02x&Prot_%02x",
                          DeviceDescriptor->bDeviceClass, DeviceDescriptor->bDeviceSubClass, DeviceDescriptor->bDeviceProtocol) + 1;
            Index += swprintf(&Buffer[Index],
                          L"USB\\Class_%02x&SubClass_%02x",
                          DeviceDescriptor->bDeviceClass, DeviceDescriptor->bDeviceSubClass) + 1;
            Index += swprintf(&Buffer[Index],
                          L"USB\\Class_%02x",
                          DeviceDescriptor->bDeviceClass) + 1;
        }
    }

    //
    // now allocate the buffer
    //
    DeviceString = ExAllocatePool(NonPagedPool, (Index + 1) * sizeof(WCHAR));
    if (!DeviceString)
    {
        //
        // no memory
        //
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // copy buffer
    //
    RtlCopyMemory(DeviceString, Buffer, Index * sizeof(WCHAR));
    DeviceString[Index] = UNICODE_NULL;
    UsbChildExtension->usCompatibleIds.Buffer = DeviceString;
    UsbChildExtension->usCompatibleIds.Length = Index * sizeof(WCHAR);
    UsbChildExtension->usCompatibleIds.MaximumLength = (Index + 1) * sizeof(WCHAR);
    DPRINT("usCompatibleIds %wZ\n", &UsbChildExtension->usCompatibleIds);

    //
    // Construct DeviceId string
    //
    Index = swprintf(Buffer, L"USB\\Vid_%04x&Pid_%04x", UsbChildExtension->DeviceDesc.idVendor, UsbChildExtension->DeviceDesc.idProduct) + 1;

    //
    // now allocate the buffer
    //
    DeviceString = ExAllocatePool(NonPagedPool, Index * sizeof(WCHAR));
    if (!DeviceString)
    {
        //
        // no memory
        //
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // copy buffer
    //
    RtlCopyMemory(DeviceString, Buffer, Index * sizeof(WCHAR));
    UsbChildExtension->usDeviceId.Buffer = DeviceString;
    UsbChildExtension->usDeviceId.Length = (Index-1) * sizeof(WCHAR);
    UsbChildExtension->usDeviceId.MaximumLength = Index * sizeof(WCHAR);
    DPRINT("usDeviceId %wZ\n", &UsbChildExtension->usDeviceId);

    //
    // Construct HardwareIds
    //
    Index = 0;
    Index += swprintf(&Buffer[Index],
                      L"USB\\Vid_%04x&Pid_%04x&Rev_%04x",
                      UsbChildExtension->DeviceDesc.idVendor, UsbChildExtension->DeviceDesc.idProduct, UsbChildExtension->DeviceDesc.bcdDevice) + 1;
    Index += swprintf(&Buffer[Index],
                      L"USB\\Vid_%04x&Pid_%04x",
                      UsbChildExtension->DeviceDesc.idVendor, UsbChildExtension->DeviceDesc.idProduct) + 1;

    //
    // now allocate the buffer
    //
    DeviceString = ExAllocatePool(NonPagedPool, (Index + 1) * sizeof(WCHAR));
    if (!DeviceString)
    {
        //
        // no memory
        //
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // copy buffer
    //
    RtlCopyMemory(DeviceString, Buffer, Index * sizeof(WCHAR));
    DeviceString[Index] = UNICODE_NULL;
    UsbChildExtension->usHardwareIds.Buffer = DeviceString;
    UsbChildExtension->usHardwareIds.Length = (Index + 1) * sizeof(WCHAR);
    UsbChildExtension->usHardwareIds.MaximumLength = (Index + 1) * sizeof(WCHAR);
    DPRINT("usHardWareIds %wZ\n", &UsbChildExtension->usHardwareIds);

    //
    // FIXME: Handle Lang ids
    //

    //
    // Get the product string if obe provided
    //
    if (UsbChildExtension->DeviceDesc.iProduct)
    {
        Status = GetUsbStringDescriptor(UsbChildDeviceObject,
                                        UsbChildExtension->DeviceDesc.iProduct,
                                        0,
                                        (PVOID*)&UsbChildExtension->usTextDescription.Buffer,
                                        &UsbChildExtension->usTextDescription.Length);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("USBHUB: GetUsbStringDescriptor failed with status %x\n", Status);
            RtlInitUnicodeString(&UsbChildExtension->usTextDescription, L"USB Device"); // FIXME NON-NLS
        }
        else
        {
            UsbChildExtension->usTextDescription.MaximumLength = UsbChildExtension->usTextDescription.Length;
            DPRINT("Usb TextDescription %wZ\n", &UsbChildExtension->usTextDescription);
        }
    }

    //
    // Get the Serial Number string if obe provided
    //
    if (UsbChildExtension->DeviceDesc.iSerialNumber)
    {
       LPWSTR SerialBuffer = NULL;

        Status = GetUsbStringDescriptor(UsbChildDeviceObject,
                                        UsbChildExtension->DeviceDesc.iSerialNumber,
                                        0,
                                        (PVOID*)&SerialBuffer,
                                        &UsbChildExtension->usInstanceId.Length);
        if (NT_SUCCESS(Status))
        {
            // construct instance id buffer
            Index = swprintf(Buffer, L"%04d&%s", HubDeviceExtension->InstanceCount, SerialBuffer) + 1;
            UsbChildExtension->usInstanceId.Buffer = (LPWSTR)ExAllocatePool(NonPagedPool, Index * sizeof(WCHAR));
            if (UsbChildExtension->usInstanceId.Buffer == NULL)
            {
                DPRINT1("Error: failed to allocate %lu bytes\n", Index * sizeof(WCHAR));
                return STATUS_INSUFFICIENT_RESOURCES;
            }

            //
            // copy instance id
            //
            RtlCopyMemory(UsbChildExtension->usInstanceId.Buffer, Buffer, Index * sizeof(WCHAR));
            UsbChildExtension->usInstanceId.Length = UsbChildExtension->usInstanceId.MaximumLength = Index * sizeof(WCHAR);
            ExFreePool(SerialBuffer);

            DPRINT("Usb InstanceId %wZ InstanceCount %x\n", &UsbChildExtension->usInstanceId, HubDeviceExtension->InstanceCount);
            return Status;
        }
    }

    //
    // the device did not provide a serial number, or failed to retrieve the serial number
    // lets create a pseudo instance id
    //
    Index = swprintf(Buffer, L"%04d&%04d", HubDeviceExtension->InstanceCount, UsbChildExtension->PortNumber) + 1;
    UsbChildExtension->usInstanceId.Buffer = (LPWSTR)ExAllocatePool(NonPagedPool, Index * sizeof(WCHAR));
    if (UsbChildExtension->usInstanceId.Buffer == NULL)
    {
       DPRINT1("Error: failed to allocate %lu bytes\n", Index * sizeof(WCHAR));
       Status = STATUS_INSUFFICIENT_RESOURCES;
       return Status;
    }

    //
    // copy instance id
    //
    RtlCopyMemory(UsbChildExtension->usInstanceId.Buffer, Buffer, Index * sizeof(WCHAR));
    UsbChildExtension->usInstanceId.Length = UsbChildExtension->usInstanceId.MaximumLength = Index * sizeof(WCHAR);

    DPRINT("usDeviceId %wZ\n", &UsbChildExtension->usInstanceId);
    return STATUS_SUCCESS;
}

NTSTATUS
DestroyUsbChildDeviceObject(
    IN PDEVICE_OBJECT UsbHubDeviceObject,
    IN LONG PortId)
{
    PHUB_DEVICE_EXTENSION HubDeviceExtension = (PHUB_DEVICE_EXTENSION)UsbHubDeviceObject->DeviceExtension;
    PHUB_CHILDDEVICE_EXTENSION UsbChildExtension = NULL;
    PDEVICE_OBJECT ChildDeviceObject = NULL;
    ULONG Index = 0;

    DPRINT("Removing device on port %d (Child index: %d)\n", PortId, Index);

    for (Index = 0; Index < USB_MAXCHILDREN; Index++)
    {
        if (HubDeviceExtension->ChildDeviceObject[Index])
        {
            UsbChildExtension = (PHUB_CHILDDEVICE_EXTENSION)HubDeviceExtension->ChildDeviceObject[Index]->DeviceExtension;

            /* Check if it matches the port ID */
            if (UsbChildExtension->PortNumber == PortId)
            {
                /* We found it */
                ChildDeviceObject = HubDeviceExtension->ChildDeviceObject[Index];
                break;
            }
        }
    }

    /* Fail the request if the device doesn't exist */
    if (!ChildDeviceObject)
    {
        DPRINT1("Removal request for non-existant device!\n");
        return STATUS_UNSUCCESSFUL;
    }

    /* Remove the device from the table */
    HubDeviceExtension->ChildDeviceObject[Index] = NULL;

    /* Invalidate device relations for the root hub */
    IoInvalidateDeviceRelations(HubDeviceExtension->RootHubPhysicalDeviceObject, BusRelations);

    /* The rest of the removal process takes place in IRP_MN_REMOVE_DEVICE handling for the PDO */
    return STATUS_SUCCESS;
}

NTSTATUS
CreateUsbChildDeviceObject(
    IN PDEVICE_OBJECT UsbHubDeviceObject,
    IN LONG PortId,
    OUT PDEVICE_OBJECT *UsbChildDeviceObject,
    IN ULONG PortStatus)
{
    NTSTATUS Status;
    PDEVICE_OBJECT RootHubDeviceObject, NewChildDeviceObject;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;
    PHUB_CHILDDEVICE_EXTENSION UsbChildExtension;
    PUSB_BUS_INTERFACE_HUB_V5 HubInterface;
    ULONG ChildDeviceCount, UsbDeviceNumber = 0;
    WCHAR CharDeviceName[64];
    UNICODE_STRING DeviceName;
    ULONG ConfigDescSize, DeviceDescSize, DeviceInfoSize;
    PVOID HubInterfaceBusContext;
    USB_CONFIGURATION_DESCRIPTOR ConfigDesc;

    HubDeviceExtension = (PHUB_DEVICE_EXTENSION) UsbHubDeviceObject->DeviceExtension;
    HubInterface = &HubDeviceExtension->HubInterface;
    RootHubDeviceObject = HubDeviceExtension->RootHubPhysicalDeviceObject;
    HubInterfaceBusContext = HubDeviceExtension->UsbDInterface.BusContext;
    //
    // Find an empty slot in the child device array
    //
    for (ChildDeviceCount = 0; ChildDeviceCount < USB_MAXCHILDREN; ChildDeviceCount++)
    {
        if (HubDeviceExtension->ChildDeviceObject[ChildDeviceCount] == NULL)
        {
        DPRINT("Found unused entry at %d\n", ChildDeviceCount);
            break;
        }
    }

    //
    // Check if the limit has been reached for maximum usb devices
    //
    if (ChildDeviceCount == USB_MAXCHILDREN)
    {
        DPRINT1("USBHUB: Too many child devices!\n");
        return STATUS_UNSUCCESSFUL;
    }

    while (TRUE)
    {
        //
        // Create a Device Name
        //
        swprintf(CharDeviceName, L"\\Device\\USBPDO-%d", UsbDeviceNumber);

        //
        // Initialize UnicodeString
        //
        RtlInitUnicodeString(&DeviceName, CharDeviceName);

        //
        // Create a DeviceObject
        //
        Status = IoCreateDevice(UsbHubDeviceObject->DriverObject,
                                sizeof(HUB_CHILDDEVICE_EXTENSION),
                                NULL,
                                FILE_DEVICE_CONTROLLER,
                                FILE_AUTOGENERATED_DEVICE_NAME,
                                FALSE,
                                &NewChildDeviceObject);

        //
        // Check if the name is already in use
        //
        if ((Status == STATUS_OBJECT_NAME_EXISTS) || (Status == STATUS_OBJECT_NAME_COLLISION))
        {
            //
            // Try next name
            //
            UsbDeviceNumber++;
            continue;
        }

        //
        // Check for other errors
        //
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("USBHUB: IoCreateDevice failed with status %x\n", Status);
            return Status;
        }

        DPRINT("USBHUB: Created Device %x\n", NewChildDeviceObject);
        break;
    }

    NewChildDeviceObject->Flags |= DO_BUS_ENUMERATED_DEVICE;

    //
    // Assign the device extensions
    //
    UsbChildExtension = (PHUB_CHILDDEVICE_EXTENSION)NewChildDeviceObject->DeviceExtension;
    RtlZeroMemory(UsbChildExtension, sizeof(HUB_CHILDDEVICE_EXTENSION));
    UsbChildExtension->ParentDeviceObject = UsbHubDeviceObject;
    UsbChildExtension->PortNumber = PortId;

    // copy device interface
    RtlCopyMemory(&UsbChildExtension->DeviceInterface, &HubDeviceExtension->DeviceInterface, sizeof(USB_BUS_INTERFACE_USBDI_V2));


    //
    // Create the UsbDeviceObject
    //
    Status = HubInterface->CreateUsbDevice(HubInterfaceBusContext,
                                           (PVOID)&UsbChildExtension->UsbDeviceHandle,
                                           HubDeviceExtension->RootHubHandle,
                                           PortStatus,
                                           PortId);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("USBHUB: CreateUsbDevice failed with status %x\n", Status);
        goto Cleanup;
    }

    // copy device interface
    RtlCopyMemory(&UsbChildExtension->DeviceInterface, &HubDeviceExtension->DeviceInterface, sizeof(USB_BUS_INTERFACE_USBDI_V2));

    // FIXME replace buscontext
    UsbChildExtension->DeviceInterface.BusContext = UsbChildExtension->UsbDeviceHandle;

    //
    // Initialize UsbDevice
    //
    Status = HubInterface->InitializeUsbDevice(HubInterfaceBusContext, UsbChildExtension->UsbDeviceHandle);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("USBHUB: InitializeUsbDevice failed with status %x\n", Status);
        goto Cleanup;
    }

    DPRINT("Usb Device Handle %x\n", UsbChildExtension->UsbDeviceHandle);

    ConfigDescSize = sizeof(USB_CONFIGURATION_DESCRIPTOR);
    DeviceDescSize = sizeof(USB_DEVICE_DESCRIPTOR);

    //
    // Get the descriptors
    //
    Status = HubInterface->GetUsbDescriptors(HubInterfaceBusContext,
                                             UsbChildExtension->UsbDeviceHandle,
                                             (PUCHAR)&UsbChildExtension->DeviceDesc,
                                             &DeviceDescSize,
                                             (PUCHAR)&ConfigDesc,
                                             &ConfigDescSize);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("USBHUB: GetUsbDescriptors failed with status %x\n", Status);
        goto Cleanup;
    }

    //DumpDeviceDescriptor(&UsbChildExtension->DeviceDesc);
    //DumpConfigurationDescriptor(&ConfigDesc);

    //
    // FIXME: Support more than one configuration and one interface?
    //
    if (UsbChildExtension->DeviceDesc.bNumConfigurations > 1)
    {
        DPRINT1("Warning: Device has more than one configuration. Only one configuration (the first) is supported!\n");
    }

    if (ConfigDesc.bNumInterfaces > 1)
    {
        DPRINT1("Warning: Device has more than one interface. Only one interface (the first) is currently supported\n");
    }

    ConfigDescSize = ConfigDesc.wTotalLength;

    //
    // Allocate memory for the first full descriptor, including interfaces and endpoints.
    //
    UsbChildExtension->FullConfigDesc = ExAllocatePoolWithTag(PagedPool, ConfigDescSize, USB_HUB_TAG);

    //
    // Retrieve the full configuration descriptor
    //
    Status = GetUsbDeviceDescriptor(NewChildDeviceObject,
                                    USB_CONFIGURATION_DESCRIPTOR_TYPE,
                                    0,
                                    0,
                                    UsbChildExtension->FullConfigDesc,
                                    ConfigDescSize);

    if (!NT_SUCCESS(Status))
    {
        DPRINT1("USBHUB: GetUsbDeviceDescriptor failed with status %x\n", Status);
        goto Cleanup;
    }

    // query device details
    Status = HubInterface->QueryDeviceInformation(HubInterfaceBusContext,
                                         UsbChildExtension->UsbDeviceHandle,
                                         &UsbChildExtension->DeviceInformation,
                                         sizeof(USB_DEVICE_INFORMATION_0),
                                         &DeviceInfoSize);


    //DumpFullConfigurationDescriptor(UsbChildExtension->FullConfigDesc);

    //
    // Construct all the strings that will describe the device to PNP
    //
    Status = CreateDeviceIds(NewChildDeviceObject);
    if (!NT_SUCCESS(Status))
    {
        DPRINT1("Failed to create strings needed to describe device to PNP.\n");
        goto Cleanup;
    }

    HubDeviceExtension->ChildDeviceObject[ChildDeviceCount] = NewChildDeviceObject;
    HubDeviceExtension->InstanceCount++;

    IoInvalidateDeviceRelations(RootHubDeviceObject, BusRelations);
    return STATUS_SUCCESS;

Cleanup:

    //
    // Remove the usb device if it was created
    //
    if (UsbChildExtension->UsbDeviceHandle)
        HubInterface->RemoveUsbDevice(HubInterfaceBusContext, UsbChildExtension->UsbDeviceHandle, 0);

    //
    // Free full configuration descriptor if one was allocated
    //
    if (UsbChildExtension->FullConfigDesc)
        ExFreePool(UsbChildExtension->FullConfigDesc);

    //
    // Delete the device object
    //
    IoDeleteDevice(NewChildDeviceObject);
    return Status;
}

NTSTATUS
USBHUB_FdoQueryBusRelations(
    IN PDEVICE_OBJECT DeviceObject,
    OUT PDEVICE_RELATIONS* pDeviceRelations)
{
    PHUB_DEVICE_EXTENSION HubDeviceExtension;
    PDEVICE_RELATIONS DeviceRelations;
    ULONG i;
    ULONG Children = 0;
    ULONG NeededSize;

    HubDeviceExtension = (PHUB_DEVICE_EXTENSION)DeviceObject->DeviceExtension;

    //
    // Count the number of children
    //
    for (i = 0; i < USB_MAXCHILDREN; i++)
    {

        if (HubDeviceExtension->ChildDeviceObject[i] == NULL)
        {
            continue;
        }
        Children++;
    }

    NeededSize = sizeof(DEVICE_RELATIONS);
    if (Children > 1)
        NeededSize += (Children - 1) * sizeof(PDEVICE_OBJECT);

    //
    // Allocate DeviceRelations
    //
    DeviceRelations = (PDEVICE_RELATIONS)ExAllocatePool(PagedPool,
                                                        NeededSize);

    if (!DeviceRelations)
        return STATUS_INSUFFICIENT_RESOURCES;
    DeviceRelations->Count = Children;
    Children = 0;

    //
    // Fill in return structure
    //
    for (i = 0; i < USB_MAXCHILDREN; i++)
    {
        if (HubDeviceExtension->ChildDeviceObject[i])
        {
            ObReferenceObject(HubDeviceExtension->ChildDeviceObject[i]);
            HubDeviceExtension->ChildDeviceObject[i]->Flags &= ~DO_DEVICE_INITIALIZING;
            DeviceRelations->Objects[Children++] = HubDeviceExtension->ChildDeviceObject[i];
        }
    }

    ASSERT(Children == DeviceRelations->Count);
    *pDeviceRelations = DeviceRelations;

    return STATUS_SUCCESS;
}

VOID
NTAPI
RootHubInitCallbackFunction(
    PVOID Context)
{
    PDEVICE_OBJECT DeviceObject = (PDEVICE_OBJECT)Context;
    NTSTATUS Status;
    ULONG PortId;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;
    PORT_STATUS_CHANGE StatusChange;

    HubDeviceExtension = (PHUB_DEVICE_EXTENSION) DeviceObject->DeviceExtension;

    DPRINT("RootHubInitCallbackFunction Sending the initial SCE Request %x\n", DeviceObject);

    //
    // Send the first SCE Request
    //
    QueryStatusChangeEndpoint(DeviceObject);

    for (PortId = 1; PortId <= HubDeviceExtension->HubDescriptor.bNumberOfPorts; PortId++)
    {
        //
        // get port status
        //
        Status = GetPortStatusAndChange(HubDeviceExtension->RootHubPhysicalDeviceObject, PortId, &StatusChange);
        if (NT_SUCCESS(Status))
        {
            //
            // is there a device connected
            //
            if (StatusChange.Status & USB_PORT_STATUS_CONNECT)
            {
                //
                // reset port
                //
                Status = SetPortFeature(HubDeviceExtension->RootHubPhysicalDeviceObject, PortId, PORT_RESET);
                if (!NT_SUCCESS(Status))
                {
                    DPRINT1("Failed to reset on port %d\n", PortId);
                }
                else
                {
                    //
                    // wait for the reset to be handled since we want to enumerate synchronously
                    //
                    KeWaitForSingleObject(&HubDeviceExtension->ResetComplete,
                                          Executive,
                                          KernelMode,
                                          FALSE,
                                          NULL);
                    KeClearEvent(&HubDeviceExtension->ResetComplete);
                }
            }
        }
    }
}

BOOLEAN
USBHUB_IsRootHubFDO(
   IN PDEVICE_OBJECT DeviceObject)
{
    NTSTATUS Status;
    PDEVICE_OBJECT RootHubPhysicalDeviceObject = NULL;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;

    // get hub device extension
    HubDeviceExtension = (PHUB_DEVICE_EXTENSION) DeviceObject->DeviceExtension;

    // Get the Root Hub Pdo
    Status = SubmitRequestToRootHub(HubDeviceExtension->LowerDeviceObject,
                                    IOCTL_INTERNAL_USB_GET_ROOTHUB_PDO,
                                    &RootHubPhysicalDeviceObject,
                                    NULL);

    // FIXME handle error
    ASSERT(NT_SUCCESS(Status));

    // physical device object is only obtained for root hubs
    return (RootHubPhysicalDeviceObject != NULL);
}


NTSTATUS
USBHUB_FdoStartDevice(
   IN PDEVICE_OBJECT DeviceObject,
   IN PIRP Irp)
{
    PURB Urb;
    PUSB_INTERFACE_DESCRIPTOR Pid;
    ULONG Result = 0, PortId;
    USBD_INTERFACE_LIST_ENTRY InterfaceList[2] = {{NULL, NULL}, {NULL, NULL}};
    PURB ConfigUrb = NULL;
    ULONG HubStatus;
    NTSTATUS Status = STATUS_SUCCESS;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;
    PDEVICE_OBJECT RootHubDeviceObject;
    PVOID HubInterfaceBusContext;
    PORT_STATUS_CHANGE StatusChange;

    // get hub device extension
    HubDeviceExtension = (PHUB_DEVICE_EXTENSION) DeviceObject->DeviceExtension;

    DPRINT("IRP_MJ_PNP / IRP_MN_START_DEVICE\n");

    // Allocated size including the sizeof USBD_INTERFACE_LIST_ENTRY
    Urb = ExAllocatePoolWithTag(NonPagedPool, sizeof(URB) + sizeof(USBD_INTERFACE_LIST_ENTRY), USB_HUB_TAG);
    if (!Urb)
    {
         // no memory
         return STATUS_INSUFFICIENT_RESOURCES;
    }

    // zero urb
    RtlZeroMemory(Urb, sizeof(URB) + sizeof(USBD_INTERFACE_LIST_ENTRY));

    // Get the Root Hub Pdo
    Status = SubmitRequestToRootHub(HubDeviceExtension->LowerDeviceObject,
                                    IOCTL_INTERNAL_USB_GET_ROOTHUB_PDO,
                                    &HubDeviceExtension->RootHubPhysicalDeviceObject,
                                    &HubDeviceExtension->RootHubFunctionalDeviceObject);
    if (!NT_SUCCESS(Status))
    {
        // failed to obtain hub pdo
        DPRINT1("IOCTL_INTERNAL_USB_GET_ROOTHUB_PDO failed with %x\n", Status);
        ExFreePool(Urb);
        return Status;
    }

    // sanity checks
    ASSERT(HubDeviceExtension->RootHubPhysicalDeviceObject);
    ASSERT(HubDeviceExtension->RootHubFunctionalDeviceObject);

    // get roothub
    RootHubDeviceObject = HubDeviceExtension->RootHubPhysicalDeviceObject;

    // Send the StartDevice to RootHub
    Status = ForwardIrpAndWait(RootHubDeviceObject, Irp);

    if (!NT_SUCCESS(Status))
    {
        // failed to start pdo
        DPRINT1("Failed to start the RootHub PDO\n");
        ExFreePool(Urb);
        return Status;
    }

    // Get the current number of hubs
    Status = SubmitRequestToRootHub(RootHubDeviceObject,
                                    IOCTL_INTERNAL_USB_GET_HUB_COUNT,
                                    &HubDeviceExtension->NumberOfHubs, NULL);
    if (!NT_SUCCESS(Status))
    {
        // failed to get number of hubs
        DPRINT1("IOCTL_INTERNAL_USB_GET_HUB_COUNT failed with %x\n", Status);
        ExFreePool(Urb);
        return Status;
    }

    // Get the Hub Interface
    Status = QueryInterface(RootHubDeviceObject,
                            USB_BUS_INTERFACE_HUB_GUID,
                            sizeof(USB_BUS_INTERFACE_HUB_V5),
                            USB_BUSIF_HUB_VERSION_5,
                            (PVOID)&HubDeviceExtension->HubInterface);

    if (!NT_SUCCESS(Status))
    {
        // failed to get root hub interface
        DPRINT1("Failed to get HUB_GUID interface with status 0x%08lx\n", Status);
        ExFreePool(Urb);
        return Status;
    }

    HubInterfaceBusContext = HubDeviceExtension->HubInterface.BusContext;

    // Get the USBDI Interface
    Status = QueryInterface(RootHubDeviceObject,
                            USB_BUS_INTERFACE_USBDI_GUID,
                            sizeof(USB_BUS_INTERFACE_USBDI_V2),
                            USB_BUSIF_USBDI_VERSION_2,
                            (PVOID)&HubDeviceExtension->UsbDInterface);

    if (!NT_SUCCESS(Status))
    {
        // failed to get usbdi interface
        DPRINT1("Failed to get USBDI_GUID interface with status 0x%08lx\n", Status);
        ExFreePool(Urb);
        return Status;
    }

    // Get Root Hub Device Handle
    Status = SubmitRequestToRootHub(RootHubDeviceObject,
                                    IOCTL_INTERNAL_USB_GET_DEVICE_HANDLE,
                                    &HubDeviceExtension->RootHubHandle,
                                    NULL);

    if (!NT_SUCCESS(Status))
    {
        // failed
        DPRINT1("IOCTL_INTERNAL_USB_GET_DEVICE_HANDLE failed with status 0x%08lx\n", Status);
        ExFreePool(Urb);
        return Status;
    }

    //
    // Get Hub Device Information
    //
    Status = HubDeviceExtension->HubInterface.QueryDeviceInformation(HubInterfaceBusContext,
                                                                        HubDeviceExtension->RootHubHandle,
                                                                        &HubDeviceExtension->DeviceInformation,
                                                                        sizeof(USB_DEVICE_INFORMATION_0),
                                                                        &Result);

    DPRINT("Status %x, Result 0x%08lx\n", Status, Result);
    DPRINT("InformationLevel %x\n", HubDeviceExtension->DeviceInformation.InformationLevel);
    DPRINT("ActualLength %x\n", HubDeviceExtension->DeviceInformation.ActualLength);
    DPRINT("PortNumber %x\n", HubDeviceExtension->DeviceInformation.PortNumber);
    DPRINT("DeviceDescriptor %x\n", HubDeviceExtension->DeviceInformation.DeviceDescriptor);
    DPRINT("HubAddress %x\n", HubDeviceExtension->DeviceInformation.HubAddress);
    DPRINT("NumberofPipes %x\n", HubDeviceExtension->DeviceInformation.NumberOfOpenPipes);

    // Get Root Hubs Device Descriptor
    UsbBuildGetDescriptorRequest(Urb,
                                    sizeof(Urb->UrbControlDescriptorRequest),
                                    USB_DEVICE_DESCRIPTOR_TYPE,
                                    0,
                                    0,
                                    &HubDeviceExtension->HubDeviceDescriptor,
                                    NULL,
                                    sizeof(USB_DEVICE_DESCRIPTOR),
                                    NULL);

    // set device handle
    Urb->UrbHeader.UsbdDeviceHandle = HubDeviceExtension->RootHubHandle;

    // get hub device descriptor
    Status = SubmitRequestToRootHub(RootHubDeviceObject,
                                    IOCTL_INTERNAL_USB_SUBMIT_URB,
                                    Urb,
                                    NULL);

    if (!NT_SUCCESS(Status))
    {
        // failed to get device descriptor of hub
        DPRINT1("Failed to get HubDeviceDescriptor!\n");
        ExFreePool(Urb);
        return Status;
    }

    // build configuration request
    UsbBuildGetDescriptorRequest(Urb,
                                    sizeof(Urb->UrbControlDescriptorRequest),
                                    USB_CONFIGURATION_DESCRIPTOR_TYPE,
                                    0,
                                    0,
                                    &HubDeviceExtension->HubConfigDescriptor,
                                    NULL,
                                    sizeof(USB_CONFIGURATION_DESCRIPTOR) + sizeof(USB_INTERFACE_DESCRIPTOR) + sizeof(USB_ENDPOINT_DESCRIPTOR),
                                    NULL);

    // set device handle
    Urb->UrbHeader.UsbdDeviceHandle = HubDeviceExtension->RootHubHandle;

    // request configuration descriptor
    Status = SubmitRequestToRootHub(RootHubDeviceObject,
                                    IOCTL_INTERNAL_USB_SUBMIT_URB,
                                    Urb,
                                    NULL);

    if (!NT_SUCCESS(Status))
    {
        // failed to get configuration descriptor
        DPRINT1("Failed to get RootHub Configuration with status %x\n", Status);
        ExFreePool(Urb);
        return Status;
    }

    // sanity checks
    ASSERT(HubDeviceExtension->HubConfigDescriptor.wTotalLength == sizeof(USB_CONFIGURATION_DESCRIPTOR) + sizeof(USB_INTERFACE_DESCRIPTOR) + sizeof(USB_ENDPOINT_DESCRIPTOR));
    ASSERT(HubDeviceExtension->HubConfigDescriptor.bDescriptorType == USB_CONFIGURATION_DESCRIPTOR_TYPE);
    ASSERT(HubDeviceExtension->HubConfigDescriptor.bLength == sizeof(USB_CONFIGURATION_DESCRIPTOR));
    ASSERT(HubDeviceExtension->HubConfigDescriptor.bNumInterfaces == 1);
    ASSERT(HubDeviceExtension->HubInterfaceDescriptor.bLength == sizeof(USB_INTERFACE_DESCRIPTOR));
    ASSERT(HubDeviceExtension->HubInterfaceDescriptor.bDescriptorType == USB_INTERFACE_DESCRIPTOR_TYPE);
    ASSERT(HubDeviceExtension->HubInterfaceDescriptor.bNumEndpoints == 1);
    ASSERT(HubDeviceExtension->HubEndPointDescriptor.bDescriptorType == USB_ENDPOINT_DESCRIPTOR_TYPE);
    ASSERT(HubDeviceExtension->HubEndPointDescriptor.bLength == sizeof(USB_ENDPOINT_DESCRIPTOR));
    ASSERT(HubDeviceExtension->HubEndPointDescriptor.bmAttributes == USB_ENDPOINT_TYPE_INTERRUPT);
    ASSERT(HubDeviceExtension->HubEndPointDescriptor.bEndpointAddress == 0x81); // interrupt in

    // get hub information
    Status = HubDeviceExtension->HubInterface.GetExtendedHubInformation(HubInterfaceBusContext,
                                                                        RootHubDeviceObject,
                                                                        &HubDeviceExtension->UsbExtHubInfo,
                                                                        sizeof(USB_EXTHUB_INFORMATION_0),
                                                                        &Result);
    if (!NT_SUCCESS(Status))
    {
        // failed to get hub information
        DPRINT1("Failed to extended hub information. Unable to determine the number of ports!\n");
        ExFreePool(Urb);
        return Status;
    }

    if (!HubDeviceExtension->UsbExtHubInfo.NumberOfPorts)
    {
        // bogus port driver
        DPRINT1("Failed to retrieve the number of ports\n");
        ExFreePool(Urb);
        return STATUS_UNSUCCESSFUL;
    }

    DPRINT("HubDeviceExtension->UsbExtHubInfo.NumberOfPorts %x\n", HubDeviceExtension->UsbExtHubInfo.NumberOfPorts);

    // Build hub descriptor request
    UsbBuildVendorRequest(Urb,
                            URB_FUNCTION_CLASS_DEVICE,
                            sizeof(Urb->UrbControlVendorClassRequest),
                            USBD_TRANSFER_DIRECTION_IN | USBD_SHORT_TRANSFER_OK,
                            0,
                            USB_REQUEST_GET_DESCRIPTOR,
                            USB_DEVICE_CLASS_RESERVED,
                            0,
                            &HubDeviceExtension->HubDescriptor,
                            NULL,
                            sizeof(USB_HUB_DESCRIPTOR),
                            NULL);

    // set device handle
    Urb->UrbHeader.UsbdDeviceHandle = HubDeviceExtension->RootHubHandle;

    // send request
    Status = SubmitRequestToRootHub(RootHubDeviceObject,
                                    IOCTL_INTERNAL_USB_SUBMIT_URB,
                                    Urb,
                                    NULL);

    if (!NT_SUCCESS(Status))
    {
        DPRINT1("Failed to get Hub Descriptor!\n");
        ExFreePool(Urb);
        return STATUS_UNSUCCESSFUL;
    }

    // sanity checks
    ASSERT(HubDeviceExtension->HubDescriptor.bDescriptorLength == sizeof(USB_HUB_DESCRIPTOR));
    ASSERT(HubDeviceExtension->HubDescriptor.bNumberOfPorts == HubDeviceExtension->UsbExtHubInfo.NumberOfPorts);
    ASSERT(HubDeviceExtension->HubDescriptor.bDescriptorType == 0x29);

    // build get status request
    HubStatus = 0;
    UsbBuildGetStatusRequest(Urb,
                                URB_FUNCTION_GET_STATUS_FROM_DEVICE,
                                0,
                                &HubStatus,
                                0,
                                NULL);
    // set device handle
    Urb->UrbHeader.UsbdDeviceHandle = HubDeviceExtension->RootHubHandle;

    // send request
    Status = SubmitRequestToRootHub(RootHubDeviceObject,
                                    IOCTL_INTERNAL_USB_SUBMIT_URB,
                                    Urb,
                                    NULL);
    if (!NT_SUCCESS(Status))
    {
        // failed to get hub status
        DPRINT1("Failed to get Hub Status!\n");
        ExFreePool(Urb);
        return STATUS_UNSUCCESSFUL;
    }

    // Allocate memory for PortStatusChange to hold 2 USHORTs for each port on hub
    HubDeviceExtension->PortStatusChange = ExAllocatePoolWithTag(NonPagedPool,
                                                                    sizeof(ULONG) * HubDeviceExtension->UsbExtHubInfo.NumberOfPorts,
                                                                    USB_HUB_TAG);

    // Get the first Configuration Descriptor
    Pid = USBD_ParseConfigurationDescriptorEx(&HubDeviceExtension->HubConfigDescriptor,
                                                &HubDeviceExtension->HubConfigDescriptor,
                                                -1, -1, -1, -1, -1);
    if (Pid == NULL)
    {
        // failed parse hub descriptor
        DPRINT1("Failed to parse configuration descriptor\n");
        ExFreePool(Urb);
        return STATUS_UNSUCCESSFUL;
    }

    // create configuration request
    InterfaceList[0].InterfaceDescriptor = Pid;
    ConfigUrb = USBD_CreateConfigurationRequestEx(&HubDeviceExtension->HubConfigDescriptor,
                                                    (PUSBD_INTERFACE_LIST_ENTRY)&InterfaceList);
    if (ConfigUrb == NULL)
    {
        // failed to build urb
        DPRINT1("Failed to allocate urb\n");
        ExFreePool(Urb);
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    // send request
    Status = SubmitRequestToRootHub(RootHubDeviceObject,
                                    IOCTL_INTERNAL_USB_SUBMIT_URB,
                                    ConfigUrb,
                                    NULL);
    if (!NT_SUCCESS(Status))
    {
        // failed to select configuration
        DPRINT1("Failed to select configuration with %x\n", Status);
        ExFreePool(Urb);
        ExFreePool(ConfigUrb);
        return Status;
    }

    // store configuration & pipe handle
    HubDeviceExtension->ConfigurationHandle = ConfigUrb->UrbSelectConfiguration.ConfigurationHandle;
    HubDeviceExtension->PipeHandle = ConfigUrb->UrbSelectConfiguration.Interface.Pipes[0].PipeHandle;
    DPRINT("Configuration Handle %x\n", HubDeviceExtension->ConfigurationHandle);

    FDO_QueryInterface(DeviceObject, &HubDeviceExtension->DeviceInterface);


    // free urb
    ExFreePool(ConfigUrb);

    // check if function is available
    if (HubDeviceExtension->UsbDInterface.IsDeviceHighSpeed)
    {
        // is it high speed bus
        if (HubDeviceExtension->UsbDInterface.IsDeviceHighSpeed(HubInterfaceBusContext))
        {
            // initialize usb 2.0 hub
            Status = HubDeviceExtension->HubInterface.Initialize20Hub(HubInterfaceBusContext,
                                                                        HubDeviceExtension->RootHubHandle, 1);
            DPRINT("Status %x\n", Status);

            // FIXME handle error
            ASSERT(Status == STATUS_SUCCESS);
        }
    }


    // Enable power on all ports
    DPRINT("Enabling PortPower on all ports!\n");
    for (PortId = 1; PortId <= HubDeviceExtension->HubDescriptor.bNumberOfPorts; PortId++)
    {
        Status = SetPortFeature(RootHubDeviceObject, PortId, PORT_POWER);
        if (!NT_SUCCESS(Status))
            DPRINT1("Failed to power on port %d\n", PortId);

        Status = ClearPortFeature(RootHubDeviceObject, PortId, C_PORT_CONNECTION);
        if (!NT_SUCCESS(Status))
            DPRINT1("Failed to power on port %d\n", PortId);
    }

    // init root hub notification
    if (HubDeviceExtension->HubInterface.RootHubInitNotification)
    {
        Status = HubDeviceExtension->HubInterface.RootHubInitNotification(HubInterfaceBusContext,
                                                                            DeviceObject,
                                                                            RootHubInitCallbackFunction);
        if (!NT_SUCCESS(Status))
        {
            DPRINT1("Failed to set callback\n");
            ExFreePool(Urb);
            return Status;
        }
    }
    else
    {
        // Send the first SCE Request
        QueryStatusChangeEndpoint(DeviceObject);

        //
        // reset ports
        //
        for (PortId = 1; PortId <= HubDeviceExtension->HubDescriptor.bNumberOfPorts; PortId++)
        {
            //
            // get port status
            //
            Status = GetPortStatusAndChange(HubDeviceExtension->RootHubPhysicalDeviceObject, PortId, &StatusChange);
            if (NT_SUCCESS(Status))
            {
                //
                // is there a device connected
                //
                if (StatusChange.Status & USB_PORT_STATUS_CONNECT)
                {
                    //
                    // reset port
                    //
                    Status = SetPortFeature(HubDeviceExtension->RootHubPhysicalDeviceObject, PortId, PORT_RESET);
                    if (!NT_SUCCESS(Status))
                    {
                        DPRINT1("Failed to reset on port %d\n", PortId);
                    }
                    else
                    {
                        //
                        // wait for the reset to be handled since we want to enumerate synchronously
                        //
                        KeWaitForSingleObject(&HubDeviceExtension->ResetComplete,
                                                Executive,
                                                KernelMode,
                                                FALSE,
                                                NULL);
                        KeClearEvent(&HubDeviceExtension->ResetComplete);
                    }
                }
            }
        }
    }

    // free urb
    ExFreePool(Urb);

    // done
    return Status;
}

NTSTATUS
USBHUB_FdoHandlePnp(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    PIO_STACK_LOCATION Stack;
    NTSTATUS Status = STATUS_SUCCESS;
    ULONG_PTR Information = 0;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;

    HubDeviceExtension = (PHUB_DEVICE_EXTENSION) DeviceObject->DeviceExtension;

    Stack = IoGetCurrentIrpStackLocation(Irp);

    switch (Stack->MinorFunction)
    {
        case IRP_MN_START_DEVICE:
        {
            if (USBHUB_IsRootHubFDO(DeviceObject))
            {
                // start root hub fdo
                Status = USBHUB_FdoStartDevice(DeviceObject, Irp);
            }
            else
            {
                Status = USBHUB_ParentFDOStartDevice(DeviceObject, Irp);
            }
            break;
        }

        case IRP_MN_QUERY_DEVICE_RELATIONS:
        {
            switch (Stack->Parameters.QueryDeviceRelations.Type)
            {
                case BusRelations:
                {
                    PDEVICE_RELATIONS DeviceRelations = NULL;
                    DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / BusRelations\n");

                    Status = USBHUB_FdoQueryBusRelations(DeviceObject, &DeviceRelations);

                    Information = (ULONG_PTR)DeviceRelations;
                    break;
                }
                case RemovalRelations:
                {
                    DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / RemovalRelations\n");
                    return ForwardIrpAndForget(DeviceObject, Irp);
                }
                default:
                    DPRINT("IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / Unknown type 0x%lx\n",
                            Stack->Parameters.QueryDeviceRelations.Type);
                    return ForwardIrpAndForget(DeviceObject, Irp);
            }
            break;
        }
        case IRP_MN_QUERY_REMOVE_DEVICE:
        case IRP_MN_QUERY_STOP_DEVICE:
        {
            Irp->IoStatus.Status = STATUS_SUCCESS;
            return ForwardIrpAndForget(DeviceObject, Irp);
        }
        case IRP_MN_REMOVE_DEVICE:
        {
            Irp->IoStatus.Status = STATUS_SUCCESS;
            IoCompleteRequest(Irp, IO_NO_INCREMENT);

            IoDetachDevice(HubDeviceExtension->LowerDeviceObject);
            IoDeleteDevice(DeviceObject);

            return STATUS_SUCCESS;
        }
        case IRP_MN_QUERY_BUS_INFORMATION:
        {
            DPRINT("IRP_MN_QUERY_BUS_INFORMATION\n");
            break;
        }
        case IRP_MN_QUERY_ID:
        {
            DPRINT("IRP_MN_QUERY_ID\n");
            break;
        }
        case IRP_MN_QUERY_CAPABILITIES:
        {
            DPRINT("IRP_MN_QUERY_CAPABILITIES\n");
            break;
        }
        default:
        {
            DPRINT(" IRP_MJ_PNP / unknown minor function 0x%lx\n", Stack->MinorFunction);
            return ForwardIrpAndForget(DeviceObject, Irp);
        }
    }

    Irp->IoStatus.Information = Information;
    Irp->IoStatus.Status = Status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);
    return Status;
}

NTSTATUS
USBHUB_FdoHandleDeviceControl(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp)
{
    PIO_STACK_LOCATION IoStack;
    NTSTATUS Status = STATUS_NOT_IMPLEMENTED;
    PUSB_NODE_INFORMATION NodeInformation;
    PHUB_DEVICE_EXTENSION HubDeviceExtension;
    PUSB_NODE_CONNECTION_INFORMATION NodeConnectionInfo;
    PHUB_CHILDDEVICE_EXTENSION ChildDeviceExtension;
    PUSB_NODE_CONNECTION_DRIVERKEY_NAME NodeKey;
    PUSB_NODE_CONNECTION_NAME ConnectionName;
    ULONG Index, Length;

    // get stack location
    IoStack = IoGetCurrentIrpStackLocation(Irp);

    // get device extension
    HubDeviceExtension = (PHUB_DEVICE_EXTENSION) DeviceObject->DeviceExtension;

    if (IoStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_USB_GET_NODE_INFORMATION)
    {
        // is the buffer big enough
        if (IoStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(USB_NODE_INFORMATION))
        {
            // buffer too small
            Status = STATUS_BUFFER_TOO_SMALL;
        }
        else
        {
            // get buffer
            NodeInformation = (PUSB_NODE_INFORMATION)Irp->AssociatedIrp.SystemBuffer;

            // sanity check
            ASSERT(NodeInformation);

            // init buffer
            NodeInformation->NodeType = UsbHub;
            RtlCopyMemory(&NodeInformation->u.HubInformation.HubDescriptor, &HubDeviceExtension->HubDescriptor, sizeof(USB_HUB_DESCRIPTOR));

            // FIXME is hub powered
            NodeInformation->u.HubInformation.HubIsBusPowered = TRUE;

            // done
            Irp->IoStatus.Information = sizeof(USB_NODE_INFORMATION);
            Status = STATUS_SUCCESS;
        }


    }
    else if (IoStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_USB_GET_NODE_CONNECTION_INFORMATION)
    {
        if (IoStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(USB_NODE_CONNECTION_INFORMATION))
        {
            // buffer too small
            Status = STATUS_BUFFER_TOO_SMALL;
        }
        else
        {
            // get node connection info
            NodeConnectionInfo = (PUSB_NODE_CONNECTION_INFORMATION)Irp->AssociatedIrp.SystemBuffer;

            // sanity checks
            ASSERT(NodeConnectionInfo);

            for(Index = 0; Index < USB_MAXCHILDREN; Index++)
            {
                if (HubDeviceExtension->ChildDeviceObject[Index] == NULL)
                    continue;

                // get child device extension
                ChildDeviceExtension = (PHUB_CHILDDEVICE_EXTENSION)HubDeviceExtension->ChildDeviceObject[Index]->DeviceExtension;

                if (ChildDeviceExtension->PortNumber != NodeConnectionInfo->ConnectionIndex)
                   continue;

                // init node connection info
                RtlCopyMemory(&NodeConnectionInfo->DeviceDescriptor, &ChildDeviceExtension->DeviceDesc, sizeof(USB_DEVICE_DESCRIPTOR));
                NodeConnectionInfo->CurrentConfigurationValue = ChildDeviceExtension->FullConfigDesc->bConfigurationValue;
                NodeConnectionInfo->DeviceIsHub = FALSE; //FIXME support hubs
                NodeConnectionInfo->LowSpeed = ChildDeviceExtension->DeviceInformation.DeviceSpeed == UsbLowSpeed;
                NodeConnectionInfo->DeviceAddress = ChildDeviceExtension->DeviceInformation.DeviceAddress;
                NodeConnectionInfo->NumberOfOpenPipes = ChildDeviceExtension->DeviceInformation.NumberOfOpenPipes;
                NodeConnectionInfo->ConnectionStatus = DeviceConnected; //FIXME

                if (NodeConnectionInfo->NumberOfOpenPipes)
                {
                    DPRINT1("Need to copy pipe information\n");
                }
                break;
            }
            // done
            Irp->IoStatus.Information = sizeof(USB_NODE_INFORMATION);
            Status = STATUS_SUCCESS;
        }
    }
    else if (IoStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME)
    {
        if (IoStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(USB_NODE_CONNECTION_INFORMATION))
        {
            // buffer too small
            Status = STATUS_BUFFER_TOO_SMALL;
        }
        else
        {
            // get node connection info
            NodeKey = (PUSB_NODE_CONNECTION_DRIVERKEY_NAME)Irp->AssociatedIrp.SystemBuffer;

            // sanity checks
            ASSERT(NodeKey);

            for(Index = 0; Index < USB_MAXCHILDREN; Index++)
            {
                if (HubDeviceExtension->ChildDeviceObject[Index] == NULL)
                    continue;

                // get child device extension
                ChildDeviceExtension = (PHUB_CHILDDEVICE_EXTENSION)HubDeviceExtension->ChildDeviceObject[Index]->DeviceExtension;

                if (ChildDeviceExtension->PortNumber != NodeKey->ConnectionIndex)
                   continue;

                // get driver key
                Status = IoGetDeviceProperty(HubDeviceExtension->ChildDeviceObject[Index], DevicePropertyDriverKeyName,
                                             IoStack->Parameters.DeviceIoControl.OutputBufferLength - sizeof(USB_NODE_CONNECTION_DRIVERKEY_NAME),
                                             NodeKey->DriverKeyName,
                                             &Length);

                if (Status == STATUS_BUFFER_TOO_SMALL)
                {
                    // normalize status
                    Status = STATUS_SUCCESS;
                }

                if (Length + sizeof(USB_NODE_CONNECTION_DRIVERKEY_NAME) > IoStack->Parameters.DeviceIoControl.OutputBufferLength)
                {
                    // terminate node key name
                    NodeKey->DriverKeyName[0] = UNICODE_NULL;
                    Irp->IoStatus.Information = sizeof(USB_NODE_CONNECTION_DRIVERKEY_NAME);
                }
                else
                {
                    // result size
                    Irp->IoStatus.Information = Length + sizeof(USB_NODE_CONNECTION_DRIVERKEY_NAME);
                }

                // length of driver name
                NodeKey->ActualLength = Length + sizeof(USB_NODE_CONNECTION_DRIVERKEY_NAME);
                break;
            }
        }
    }
    else if (IoStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_USB_GET_NODE_CONNECTION_NAME)
    {
        if (IoStack->Parameters.DeviceIoControl.OutputBufferLength < sizeof(USB_NODE_CONNECTION_NAME))
        {
            // buffer too small
            Status = STATUS_BUFFER_TOO_SMALL;
        }
        else
        {
            // FIXME support hubs
            ConnectionName = (PUSB_NODE_CONNECTION_NAME)Irp->AssociatedIrp.SystemBuffer;
            ConnectionName->ActualLength = 0;
            ConnectionName->NodeName[0] = UNICODE_NULL;

            // done
            Irp->IoStatus.Information = sizeof(USB_NODE_CONNECTION_NAME);
            Status = STATUS_SUCCESS;
        }
    }
    else
    {
        DPRINT1("UNIMPLEMENTED FdoHandleDeviceControl IoCtl %x InputBufferLength %x OutputBufferLength %x\n", IoStack->Parameters.DeviceIoControl.IoControlCode,
           IoStack->Parameters.DeviceIoControl.InputBufferLength, IoStack->Parameters.DeviceIoControl.OutputBufferLength);
    }

    // finish irp
    Irp->IoStatus.Status = Status;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

    return Status;
}

