/*++
Copyright (c) 1996  Microsoft Corporation

Module Name:

    PARENT.C

Abstract:

    This module contains code that manages composite devices on USB.

Author:

    jdunn

Environment:

    kernel mode only

Notes:


Revision History:


--*/

#include <wdm.h>
#ifdef WMI_SUPPORT
#include <wmilib.h>
#endif /* WMI_SUPPORT */
#include <wdmguid.h>
#include "usbhub.h"


#define COMP_RESET_TIMEOUT  3000     // Timeout in ms (3 sec)


#ifdef PAGE_CODE
#ifdef ALLOC_PRAGMA
#pragma alloc_text(PAGE, USBH_ParentFdoStopDevice)
#pragma alloc_text(PAGE, USBH_ParentFdoRemoveDevice)
#pragma alloc_text(PAGE, UsbhParentFdoCleanup)
#pragma alloc_text(PAGE, USBH_ParentQueryBusRelations)
#pragma alloc_text(PAGE, USBH_ParentFdoStartDevice)
#pragma alloc_text(PAGE, USBH_FunctionPdoQueryId)
#pragma alloc_text(PAGE, USBH_FunctionPdoQueryDeviceText)
#pragma alloc_text(PAGE, USBH_FunctionPdoPnP)
#pragma alloc_text(PAGE, USBH_ParentCreateFunctionList)
#endif
#endif

VOID
UsbhParentFdoCleanup(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent
    )
 /* ++
  *
  * Description:
  *
  * This routine is called to shut down the hub.
  *
  * Argument:
  *
  * Return:
  *
  * STATUS_SUCCESS
  *
  * -- */
{
    PDEVICE_OBJECT deviceObject;
    PSINGLE_LIST_ENTRY listEntry;
    ULONG i;
    PDEVICE_EXTENSION_FUNCTION deviceExtensionFunction;
    KIRQL irql;
    PIRP wWIrp;

    USBH_KdPrint((2,"'UsbhParentFdoCleanup Fdo extension %x\n",
        DeviceExtensionParent));

    LOGENTRY(LOG_PNP, "pfdc", DeviceExtensionParent,
                DeviceExtensionParent->PendingWakeIrp,
                0);

    //
    // dump our wake request
    //
    IoAcquireCancelSpinLock(&irql);

    if (DeviceExtensionParent->PendingWakeIrp) {
        USBH_ASSERT(DeviceExtensionParent->ParentFlags & HUBFLAG_PENDING_WAKE_IRP);

        wWIrp = DeviceExtensionParent->PendingWakeIrp;
        IoSetCancelRoutine(wWIrp, NULL);
        DeviceExtensionParent->PendingWakeIrp = NULL;
        IoReleaseCancelSpinLock(irql);

        IoCancelIrp(wWIrp);
    } else {
        IoReleaseCancelSpinLock(irql);
    }

    if (DeviceExtensionParent->ConfigurationDescriptor) {
        UsbhExFreePool(DeviceExtensionParent->ConfigurationDescriptor);
        DeviceExtensionParent->ConfigurationDescriptor = NULL;
    }

    USBH_ParentCompleteFunctionWakeIrps (DeviceExtensionParent,
                                         STATUS_DELETE_PENDING);

    do {
        listEntry = PopEntryList(&DeviceExtensionParent->FunctionList);

        LOGENTRY(LOG_PNP, "dFU1", 0, listEntry, 0);

        if (listEntry != NULL) {

            deviceExtensionFunction =
                CONTAINING_RECORD(listEntry,
                                  DEVICE_EXTENSION_FUNCTION,
                                  ListEntry);
            ASSERT_FUNCTION(deviceExtensionFunction);
            LOGENTRY(LOG_PNP, "dFUN", deviceExtensionFunction, 0, 0);

            for (i=0; i< deviceExtensionFunction->InterfaceCount; i++) {
                LOGENTRY(LOG_PNP, "dFUi", deviceExtensionFunction,
                    deviceExtensionFunction->FunctionInterfaceList[i].InterfaceInformation,
                    0);
                UsbhExFreePool(deviceExtensionFunction->FunctionInterfaceList[i].InterfaceInformation);
            }

            //
            // Sometimes the FunctionPhysicalDeviceObject == deviceExtensionFunction.
            // In other words the device object about to be deleted is the
            // same one being used.  So do not use the extions after it has been
            // deleted.
            //

            deviceObject = deviceExtensionFunction->FunctionPhysicalDeviceObject;
            deviceExtensionFunction->FunctionPhysicalDeviceObject = NULL;

            LOGENTRY(LOG_PNP, "dFUo", deviceExtensionFunction,
                    deviceObject,
                    0);

            IoDeleteDevice(deviceObject);

        }

    } while (listEntry != NULL);

    DeviceExtensionParent->NeedCleanup = FALSE;

    USBH_KdPrint((2,"'UsbhParentFdoCleanup done Fdo extension %x\n",
        DeviceExtensionParent));


    return;
}


NTSTATUS
USBH_ParentFdoRemoveDevice(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent,
    IN PIRP Irp
    )
 /* ++
  *
  * Description:
  *
  * Argument:
  *
  * Return:
  *
  * STATUS_SUCCESS
  *
  * -- */
{
    PDEVICE_OBJECT deviceObject;
    NTSTATUS ntStatus;

    PAGED_CODE();
    deviceObject = DeviceExtensionParent->FunctionalDeviceObject;
    USBH_KdPrint((2,"'ParentFdoRemoveDevice Fdo %x\n", deviceObject));

    DeviceExtensionParent->ParentFlags |= HUBFLAG_DEVICE_STOPPING;

    //
    // see if we need cleanup
    //
    if (DeviceExtensionParent->NeedCleanup) {
        UsbhParentFdoCleanup(DeviceExtensionParent);
    }

#ifdef WMI_SUPPORT
    // de-register with WMI
    IoWMIRegistrationControl(deviceObject,
                             WMIREG_ACTION_DEREGISTER);

#endif

    //
    // And we need to pass this message on to lower level driver
    //

    // IrpAssert: Set IRP status before passing on.
    Irp->IoStatus.Status = STATUS_SUCCESS;

    ntStatus = USBH_PassIrp(Irp, DeviceExtensionParent->TopOfStackDeviceObject);

    //
    // Detach FDO from PDO
    //
    IoDetachDevice(DeviceExtensionParent->TopOfStackDeviceObject);

    // delete FDO of the parent
    IoDeleteDevice(deviceObject);

    return ntStatus;
}


NTSTATUS
USBH_ParentCreateFunctionList(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent,
    IN PUSBD_INTERFACE_LIST_ENTRY InterfaceList,
    IN PURB Urb
    )
 /* ++
  *
  * Description:
  *
  * Argument:
  *
  * Return:
  *
  * STATUS_SUCCESS
  *
  * -- */
{
    PDEVICE_OBJECT deviceObject;
    PUSBD_INTERFACE_LIST_ENTRY interfaceList, tmp, baseInterface;
    PDEVICE_EXTENSION_FUNCTION deviceExtensionFunction;
    ULONG nameIndex = 0, numberOfInterfacesThisFunction, k;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PUSB_CONFIGURATION_DESCRIPTOR configurationDescriptor;
    UNICODE_STRING uniqueIdUnicodeString;

    PAGED_CODE();
    DeviceExtensionParent->FunctionCount = 0;
    tmp = interfaceList = InterfaceList;

    DeviceExtensionParent->FunctionList.Next = NULL;
    configurationDescriptor = DeviceExtensionParent->ConfigurationDescriptor;

    for (;;) {

        nameIndex = 0;

        if (interfaceList->InterfaceDescriptor) {

            //
            // interfaceList contains all the interfaces on the device
            // in sequential order.
            //
            //
            // We will create nodes based on the following criteria:
            //
            // For each interface create one function (node)
            //
            //
            // For Each group of class/subclass interface create one
            // node iff the class is audio
            //
            // This means:
            // **
            //  Class=1
            //      subclass=0
            //  Class=1
            //      subclass=0
            // creates 2 nodes
            //
            // ** we will only do this for the audio class
            // **
            //  Class=1
            //      subclass=0
            //  Class=2
            //      subclass=1
            // creates 2 nodes
            //
            // **
            //  Class=1
            //      subclass=0
            //  Class=1
            //      subclass=1
            // creates 1 node

            //
            // Create the node to represent this device
            //

            do {
                if (NT_SUCCESS(ntStatus)) {
                    ntStatus = IoCreateDevice(UsbhDriverObject,    // Driver Object
                                     sizeof(DEVICE_EXTENSION_FUNCTION),    // Device Extension size
                                     NULL, // Device name
                                     FILE_DEVICE_UNKNOWN,  // Device Type
                                                    // should look device
                                                    // class
                                     FILE_AUTOGENERATED_DEVICE_NAME,// Device Chars
                                     FALSE,    // Exclusive
                                     &deviceObject);  // Bus Device Object

                }
                nameIndex++;
            } while (ntStatus == STATUS_OBJECT_NAME_COLLISION);


            if (!NT_SUCCESS(ntStatus)) {
                USBH_KdTrap(("IoCreateDevice for function fail\n"));
                USBH_ASSERT(deviceObject == NULL);
                deviceExtensionFunction = NULL;
                // bail on whole node
                break;
            }

            deviceObject->StackSize =
            DeviceExtensionParent->TopOfStackDeviceObject->StackSize + 1;
                USBH_KdPrint((2,"'CreateFunctionPdo StackSize=%d\n", deviceObject->StackSize));

            deviceExtensionFunction =
                    (PDEVICE_EXTENSION_FUNCTION) deviceObject->DeviceExtension;

            RtlFillMemory(deviceExtensionFunction,
                          sizeof(PDEVICE_EXTENSION_FUNCTION),
                          0);

            //
            // initialize this function extension
            //
            deviceExtensionFunction->ConfigurationHandle =
                Urb->UrbSelectConfiguration.ConfigurationHandle;

            deviceExtensionFunction->FunctionPhysicalDeviceObject =
                deviceObject;

            deviceExtensionFunction->ExtensionType =
                EXTENSION_TYPE_FUNCTION;

            deviceExtensionFunction->DeviceExtensionParent =
                DeviceExtensionParent;

            //
            // remember the base interface for this function
            //
            baseInterface = interfaceList;

            USBH_KdPrint((2,"baseInterface = %x config descr = %x\n",
                baseInterface, configurationDescriptor));

            //
            // now compile the group of interfaces that will make up
            // this function.
            //
            {
            PUSBD_INTERFACE_LIST_ENTRY interface;

            interface = interfaceList;
            interface++;

            numberOfInterfacesThisFunction = 1;
            while (interface->InterfaceDescriptor) {
                if ((interface->InterfaceDescriptor->bInterfaceClass !=
                     baseInterface->InterfaceDescriptor->bInterfaceClass) ||
                    (interface->InterfaceDescriptor->bInterfaceSubClass ==
                     baseInterface->InterfaceDescriptor->bInterfaceSubClass) ||
                    (interface->InterfaceDescriptor->bInterfaceClass !=
                     USB_DEVICE_CLASS_AUDIO)) {
                    break;
                }
                numberOfInterfacesThisFunction++;
                interface++;
            }

            USBH_ASSERT(numberOfInterfacesThisFunction <=
                USBH_MAX_FUNCTION_INTERFACES);

            }

            //
            // now we know how many interfaces we are dealing with
            //

            deviceExtensionFunction->InterfaceCount = 0;

            for (k=0; k< numberOfInterfacesThisFunction; k++) {

                PFUNCTION_INTERFACE functionInterface;

                functionInterface =
                    &deviceExtensionFunction->FunctionInterfaceList[deviceExtensionFunction->InterfaceCount];

                if (functionInterface->InterfaceInformation =
                    UsbhExAllocatePool(NonPagedPool,
                                       interfaceList->Interface->Length)) {

                    RtlCopyMemory(functionInterface->InterfaceInformation,
                                  interfaceList->Interface,
                                  interfaceList->Interface->Length);

                    functionInterface->InterfaceDescriptor
                        = interfaceList->InterfaceDescriptor;

                    //
                    // calculate the length of this interface now
                    //
                    // the length of the descriptor is the difference
                    // between the start of this interface and the
                    // start of the next one.
                    //

                    {
                    PUCHAR start, end;
                    PUSBD_INTERFACE_LIST_ENTRY tmp;

                    tmp = interfaceList;
                    tmp++;

                    end = (PUCHAR) configurationDescriptor;
                    end += configurationDescriptor->wTotalLength;

                    start = (PUCHAR) functionInterface->InterfaceDescriptor;

                    if (tmp->InterfaceDescriptor) {
                        end = (PUCHAR) tmp->InterfaceDescriptor;
                    }

                    USBH_ASSERT(end > start);
                    functionInterface->InterfaceDescriptorLength =
                        (ULONG)(end - start);
                    }

                    USBH_KdPrint((2,"functionInterface = %x\n",
                        functionInterface));

                    deviceExtensionFunction->InterfaceCount++;
                } else {
                    USBH_KdTrap(("failure to create function interface\n"));
                }

                interfaceList++;
            }

            //
            // use the interface number from our 'base' interface
            // for the unique id
            //

            RtlInitUnicodeString(&uniqueIdUnicodeString,
                     &deviceExtensionFunction->UniqueIdString[0]);

            uniqueIdUnicodeString.MaximumLength =
                     sizeof(deviceExtensionFunction->UniqueIdString);

            ntStatus = RtlIntegerToUnicodeString(
                (ULONG) baseInterface->InterfaceDescriptor->bInterfaceNumber,
                10,
                &uniqueIdUnicodeString);

            //
            // add this function to the list
            //

            DeviceExtensionParent->FunctionCount++;

            PushEntryList(&DeviceExtensionParent->FunctionList,
                          &deviceExtensionFunction->ListEntry);

            USBH_KdPrint((2,"deviceExtensionFunction = %x\n", deviceExtensionFunction));

            deviceObject->Flags |= DO_POWER_PAGABLE;
            deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
        } else {
            // end of interface list
            break;
        }
    } /* for */

    return STATUS_SUCCESS;
}


NTSTATUS
USBH_ParentFdoStopDevice(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent,
    IN PIRP Irp
    )
 /* ++
  *
  * Description:
  *
  * Argument:
  *
  * Return:
  *
  * STATUS_SUCCESS
  *
  * -- */
{
    PDEVICE_OBJECT deviceObject;
    NTSTATUS ntStatus;

    PAGED_CODE();
    deviceObject = DeviceExtensionParent->FunctionalDeviceObject;
    USBH_KdPrint((2,"'ParentFdoStopDevice Fdo %x\n", deviceObject));

    //
    // set the device to the unconfigured state
    //
    ntStatus = USBH_CloseConfiguration((PDEVICE_EXTENSION_FDO) DeviceExtensionParent);

    //
    // And we need to pass this message on to lower level driver
    //

    ntStatus = USBH_PassIrp(Irp, DeviceExtensionParent->TopOfStackDeviceObject);

    return ntStatus;
}


NTSTATUS
USBH_ParentFdoStartDevice(
    IN OUT PDEVICE_EXTENSION_PARENT DeviceExtensionParent,
    IN PIRP Irp,
    IN BOOLEAN NewList
    )
 /* ++ Description:
  *
  * Argument:
  *
  * Return:
  *
  * STATUS_SUCCESS - if successful STATUS_UNSUCCESSFUL - otherwise
  *
  * -- */
{
    NTSTATUS ntStatus;
    PURB urb = NULL;
    PUSB_INTERFACE_DESCRIPTOR interfaceDescriptor;
    PUSBD_INTERFACE_LIST_ENTRY interfaceList, tmp;
    LONG numberOfInterfaces, interfaceNumber, i;
    PUSB_CONFIGURATION_DESCRIPTOR configurationDescriptor;
    ULONG nameIndex = 0;
    DEVICE_CAPABILITIES deviceCapabilities;

    PAGED_CODE();
    USBH_KdPrint((2,"'Enter Parent StartDevice\n"));
    USBH_ASSERT(EXTENSION_TYPE_PARENT == DeviceExtensionParent->ExtensionType);


    KeInitializeEvent(&DeviceExtensionParent->PnpStartEvent, NotificationEvent, FALSE);

    USBH_KdPrint((2,"'Set PnPIrp Completion Routine\n"));

    IoCopyCurrentIrpStackLocationToNext(Irp);

    IoSetCompletionRoutine(Irp,
                           USBH_PnPIrp_Complete,
                           // always pass FDO to completion routine
                           DeviceExtensionParent,
                           TRUE,
                           TRUE,
                           TRUE);

    IoCallDriver(DeviceExtensionParent->TopOfStackDeviceObject,
                 Irp);


    KeWaitForSingleObject(&DeviceExtensionParent->PnpStartEvent,
                          Suspended,
                          KernelMode,
                          FALSE,
                          NULL);

    DeviceExtensionParent->NeedCleanup = FALSE;

    // WARN STARTS OF OLD GENERIC PARENT

    UsbhWarning(NULL,
                "This device is using obsolete USB Generic Parent!\nPlease fix your INF file.\n",
                TRUE);

//    ntStatus = STATUS_DEVICE_CONFIGURATION_ERROR;
//    goto USBH_ParentFdoStartDevice_Done;


    // END WARN STARTS OF OLD GENERIC PARENT


    //
    // configure the device
    //

    // Initialize DeviceCapabilities structure in case USBH_QueryCapabilities
    // is unsuccessful.

    RtlZeroMemory(&deviceCapabilities, sizeof(DEVICE_CAPABILITIES));

    USBH_QueryCapabilities(DeviceExtensionParent->TopOfStackDeviceObject,
                           &deviceCapabilities);
    //
    // save the system state mapping
    //

    RtlCopyMemory(&DeviceExtensionParent->DeviceState[0],
                  &deviceCapabilities.DeviceState[0],
                  sizeof(DeviceExtensionParent->DeviceState));

    // always enabled for wakeup
    DeviceExtensionParent->ParentFlags |= HUBFLAG_ENABLED_FOR_WAKEUP;

    DeviceExtensionParent->DeviceWake = deviceCapabilities.DeviceWake;
    DeviceExtensionParent->SystemWake = deviceCapabilities.SystemWake;
    DeviceExtensionParent->CurrentPowerState = PowerDeviceD0;

    KeInitializeSemaphore(&DeviceExtensionParent->ParentMutex, 1, 1);

    ntStatus = USBH_GetDeviceDescriptor(DeviceExtensionParent->FunctionalDeviceObject,
                                        &DeviceExtensionParent->DeviceDescriptor);

    if (!NT_SUCCESS(ntStatus)) {
        goto USBH_ParentFdoStartDevice_Done;
    }

    if (NewList) {
        ntStatus =
            USBH_GetConfigurationDescriptor(DeviceExtensionParent->FunctionalDeviceObject,
                                            &configurationDescriptor);
    } else {
        //
        // use the old config descriptor if this is a re-start
        // the reason is that our interface structures in the function
        // extension point in to this same buffer.

        configurationDescriptor =
            DeviceExtensionParent->ConfigurationDescriptor;
    }


    if (!NT_SUCCESS(ntStatus)) {
        goto USBH_ParentFdoStartDevice_Done;
    }

    DeviceExtensionParent->ConfigurationDescriptor =
        configurationDescriptor;

    // we will likely define some registry keys to guide us
    // in the configuration of the device -- the default will
    // be to select the first congiguration and the first
    // alternate interface for each interface.
    //

    USBH_KdPrint((2,"' Parent StartDevice cd = %x\n",
        configurationDescriptor));

    DeviceExtensionParent->CurrentConfig =
        configurationDescriptor->bConfigurationValue;

    //
    // Build an interface list structure, this is an array
    // of strucutres for each interface on the device.
    // We keep a pointer to the interface descriptor for each interface
    // within the configuration descriptor
    //

    numberOfInterfaces = configurationDescriptor->bNumInterfaces;

    tmp = interfaceList =
        UsbhExAllocatePool(PagedPool, sizeof(USBD_INTERFACE_LIST_ENTRY) *
                       (numberOfInterfaces+1));

    if (tmp == NULL) {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto USBH_ParentFdoStartDevice_Done;
    }

    //
    // just grab the first alt setting we find for each interface
    //

    i = interfaceNumber = 0;

    while (i< numberOfInterfaces) {

        interfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
                        configurationDescriptor,
                        configurationDescriptor,
                        interfaceNumber,
                        0, // assume alt setting zero here
                        -1,
                        -1,
                        -1);

        if (interfaceDescriptor) {
            interfaceList->InterfaceDescriptor =
                interfaceDescriptor;
            interfaceList++;
            i++;
        }

        interfaceNumber++;
    }

    //
    // terminate the list
    //
    interfaceList->InterfaceDescriptor = NULL;

    urb = USBD_CreateConfigurationRequestEx(configurationDescriptor,
                                            tmp);

    if (urb) {

        ntStatus = USBH_FdoSyncSubmitUrb(DeviceExtensionParent->FunctionalDeviceObject, urb);

        if (NT_SUCCESS(ntStatus)) {
            if (NewList) {

                //
                // first time create our function list
                //

                ntStatus = USBH_ParentCreateFunctionList(
                                DeviceExtensionParent,
                                tmp,
                                urb);
            } else {

                //
                // update our function list with the new handles
                //

                PDEVICE_OBJECT deviceObject;
                PSINGLE_LIST_ENTRY listEntry;
                SINGLE_LIST_ENTRY tempList;
                ULONG i;
                PDEVICE_EXTENSION_FUNCTION deviceExtensionFunction;

                USBH_KdBreak(("re-init function list %x\n",
                        DeviceExtensionParent));

                deviceObject = DeviceExtensionParent->FunctionalDeviceObject;

                tempList.Next = NULL;
                //
                // process all entries in the function list
                //
                do {
                    listEntry = PopEntryList(&DeviceExtensionParent->FunctionList);

                    if (listEntry != NULL) {
                        PushEntryList(&tempList, listEntry);

                        deviceExtensionFunction =
                            CONTAINING_RECORD(listEntry,
                                              DEVICE_EXTENSION_FUNCTION,
                                              ListEntry);

                        USBH_KdPrint((2,"'re-init function %x\n",
                            deviceExtensionFunction));

                        deviceExtensionFunction->ConfigurationHandle =
                            urb->UrbSelectConfiguration.ConfigurationHandle;

                        for (i=0; i< deviceExtensionFunction->InterfaceCount; i++) {
                            //
                            // now we need to find the matching interface
                            // information from the new configuration request
                            // and attach it to the function

                            {
                            PUSBD_INTERFACE_INFORMATION interfaceInformation;

                            interfaceInformation =
                                deviceExtensionFunction->FunctionInterfaceList[i].InterfaceInformation;

                            interfaceList = tmp;
                            while (interfaceList->InterfaceDescriptor) {

                                PFUNCTION_INTERFACE functionInterface;

                                functionInterface =
                                     &deviceExtensionFunction->FunctionInterfaceList[i];

                                if (interfaceList->InterfaceDescriptor->bInterfaceNumber
                                     == interfaceInformation->InterfaceNumber) {

                                    USBH_KdPrint((2,
                                        "'re-init matched interface %d %x %x\n",
                                        interfaceInformation->InterfaceNumber,
                                        interfaceList,
                                        interfaceInformation));

                                    if (interfaceList->InterfaceDescriptor->bAlternateSetting !=
                                        interfaceInformation->AlternateSetting) {

                                        USBH_KdPrint((2,
                                            "'re-init no match alt interface %d %x %x\n",
                                            interfaceInformation->InterfaceNumber,
                                            interfaceList,
                                            interfaceInformation));

                                        // we have a different alt setting
                                        // switch our info to match the new
                                        // setting

                                        UsbhExFreePool(interfaceInformation);

                                        interfaceInformation =
                                            functionInterface ->InterfaceInformation =
                                            UsbhExAllocatePool(NonPagedPool,
                                                               interfaceList->Interface->Length);

                                        if (interfaceInformation) {
                                            RtlCopyMemory(interfaceInformation,
                                                          interfaceList->Interface,
                                                          interfaceList->Interface->Length);

                                            functionInterface->InterfaceDescriptor =
                                                interfaceList->InterfaceDescriptor;
                                        }
                                    } else {

                                        USBH_KdPrint((2,
                                            "'re-init matched alt interface %d %x %x\n",
                                            interfaceInformation->InterfaceNumber,
                                            interfaceList,
                                            interfaceInformation));

                                        USBH_ASSERT(interfaceList->Interface->Length ==
                                               interfaceInformation->Length);
                                        RtlCopyMemory(interfaceInformation,
                                                      interfaceList->Interface,
                                                      interfaceList->Interface->Length);
                                    }
                                    break;
                                }
                                interfaceList++;
                            }
                            }
                        }
                    }

                } while (listEntry != NULL);

                // now put the entries back
                do {
                    listEntry = PopEntryList(&tempList);
                    if (listEntry != NULL) {
                        PushEntryList(&DeviceExtensionParent->FunctionList, listEntry);
                    }
                } while (listEntry != NULL);
            }
        }

        ExFreePool(urb);

        //
        // Tell the OS that this PDO can have kids.
        //
//
// Workaround for PnP bug #406381 - RC3SS: Bluescreen failure when
//                                  installing/deinstalling communication ports
//
//===== Assigned by santoshj on 09/23/99 10:27:20 to kenray =====
// This is a race condition between IopInitializeSystemDrivers and
// IoInvalidateDeviceRelations. The real fix is too big a change at this
// stage of the product and has potential of exposing other problems. This
// problem can be solved if USBHUB does not invalidate device relations on
// every start which is redundant anyway (and also exposes this bug).
//
//        USBH_IoInvalidateDeviceRelations(DeviceExtensionParent->PhysicalDeviceObject,
//                                         BusRelations);

        DeviceExtensionParent->NeedCleanup = TRUE;

    } else {
        // failed to allocate URB
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }

    UsbhExFreePool(tmp);

USBH_ParentFdoStartDevice_Done:

    //
    // complete the start Irp now since we pended it with
    // our completion handler.
    //

    USBH_CompleteIrp(Irp, ntStatus);

    return ntStatus;
}


NTSTATUS
USBH_ParentQueryBusRelations(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent,
    IN PIRP Irp
    )
 /* ++
  *
  * Description:
  *
  * This function responds to Bus_Reference_Next_Device, Bus_Query_Bus_Check,
  * //Bus_Query_Id: Bus_Id, HardwareIDs, CompatibleIDs and InstanceID.
  *
  * Arguments:
  *
  * Return:
  *
  * NtStatus
  *
  * -- */
{
    PIO_STACK_LOCATION ioStack;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PDEVICE_RELATIONS deviceRelations;
    PDEVICE_OBJECT deviceObject;
    PSINGLE_LIST_ENTRY listEntry;
    PDEVICE_EXTENSION_FUNCTION deviceExtensionFunction;

    PAGED_CODE();


    USBH_KdPrint((1, "'Query Bus Relations (PAR) %x\n",
        DeviceExtensionParent->PhysicalDeviceObject));

    //
    // Get a pointer to the current location in the Irp. This is where
    // the function codes and parameters are located.
    //
    ioStack = IoGetCurrentIrpStackLocation(Irp);

    USBH_KdPrint((2,"'QueryBusRelations (parent) ext = %x\n", DeviceExtensionParent));
    USBH_KdPrint((2,"'QueryBusRelations (parent) %x\n", ioStack->Parameters.QueryDeviceRelations.Type));

    USBH_ASSERT(ioStack->Parameters.QueryDeviceRelations.Type == BusRelations);

    USBH_KdPrint((2,"'ParentQueryBusRelations enumerate device\n"));

    //
    // It should be Function device object.
    //

    USBH_ASSERT(EXTENSION_TYPE_PARENT == DeviceExtensionParent->ExtensionType);

    //
    // Must use ExAllocatePool directly here because the OS
    // will free the buffer
    //
    deviceRelations = ExAllocatePoolWithTag(PagedPool, sizeof(*deviceRelations) +
        (DeviceExtensionParent->FunctionCount - 1) * sizeof(PDEVICE_OBJECT),
        USBHUB_HEAP_TAG);

    if (deviceRelations == NULL) {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto USBH_ParentQueryBusRelations_Done;
    }

    deviceRelations->Count = 0;

    //
    // Functions on a composite device are always present
    // we just need to return the PDO
    //

    listEntry = DeviceExtensionParent->FunctionList.Next;

    while (listEntry) {

        deviceExtensionFunction =
             CONTAINING_RECORD(listEntry,
                               DEVICE_EXTENSION_FUNCTION,
                               ListEntry);

        USBH_KdPrint((2,"'deviceExtensionFunction = %x\n", deviceExtensionFunction));

        deviceObject = deviceExtensionFunction->FunctionPhysicalDeviceObject;
        ObReferenceObject(deviceObject);
        deviceObject->Flags |= DO_POWER_PAGABLE;
        deviceObject->Flags &= ~DO_DEVICE_INITIALIZING;
        deviceRelations->Objects[deviceRelations->Count] = deviceObject;
        deviceRelations->Count++;

        listEntry = listEntry->Next;
    }

USBH_ParentQueryBusRelations_Done:

    Irp->IoStatus.Information=(ULONG_PTR) deviceRelations;
    Irp->IoStatus.Status = STATUS_SUCCESS;

    USBH_KdPrint((1, "'Query Bus Relations (PAR) %x pass on\n",
        DeviceExtensionParent->PhysicalDeviceObject));

    ntStatus = USBH_PassIrp(Irp,
                            DeviceExtensionParent->TopOfStackDeviceObject);

    return ntStatus;
}


NTSTATUS
USBH_FunctionPdoQueryId(
    IN PDEVICE_EXTENSION_FUNCTION DeviceExtensionFunction,
    IN PIRP Irp
    )
 /* ++
  *
  * Description:
  *
  * This function responds to IRP_MJ_PNP, IRP_MN_QUERY_ID.
  *
  * Arguments:
  *
  * DeviceExtensionPort - should be the PDO we created for the port device Irp
  * - the Irp
  *
  * Return:
  *
  * NtStatus
  *
  * -- */
{
    PIO_STACK_LOCATION       ioStack;
    PDEVICE_EXTENSION_PARENT deviceExtensionParent;
    PDEVICE_EXTENSION_PORT   deviceExtensionPort;
    PDEVICE_EXTENSION_HUB    deviceExtensionHub;
#ifdef USB2
//    ULONG                    diagnosticFlags;
#else
    PUSBD_EXTENSION          deviceExtensionUsbd;
#endif
    USHORT                   idVendor;
    USHORT                   idProduct;
    LONG                     miId;
    NTSTATUS                 ntStatus = STATUS_SUCCESS;
    BOOLEAN                  diagnosticMode;

    PAGED_CODE();
    deviceExtensionParent = DeviceExtensionFunction->DeviceExtensionParent;

    ioStack = IoGetCurrentIrpStackLocation(Irp);

    USBH_KdPrint((2,"'IRP_MN_QUERY_ID function Pdo extension=%x\n", DeviceExtensionFunction));

    //
    // It should be physical device object.
    //

    USBH_ASSERT(EXTENSION_TYPE_FUNCTION == DeviceExtensionFunction->ExtensionType);

    // It might not be too clean to reach into the RootHubPdo USBD extension,
    // but there doesn't seem to be any other easy way to determine if diag
    // mode is on.  If diagnostic mode is on, report the VID & PID as 0xFFFF
    // so that the diagnostic driver gets loaded for each interface of the
    // device.
    //
    deviceExtensionPort = (PDEVICE_EXTENSION_PORT)deviceExtensionParent->PhysicalDeviceObject->DeviceExtension;
    deviceExtensionHub = deviceExtensionPort->DeviceExtensionHub;

#ifdef USB2
//    diagnosticFlags = USBD_GetHackFlags(deviceExtensionHub);
//    diagnosticMode = (BOOLEAN)(USBD_DEVHACK_SET_DIAG_ID & diagnosticFlags);
    diagnosticMode = FALSE;
#else
    deviceExtensionUsbd = ((PUSBD_EXTENSION)deviceExtensionHub->RootHubPdo->DeviceExtension)->TrueDeviceExtension;
    diagnosticMode = deviceExtensionUsbd->DiagnosticMode;
#endif

    if (diagnosticMode)
    {
        idVendor  = 0xFFFF;
        idProduct = 0xFFFF;
        miId      = -1;
    }
    else
    {
        idVendor  = deviceExtensionParent->DeviceDescriptor.idVendor;
        idProduct = deviceExtensionParent->DeviceDescriptor.idProduct;
        miId      = DeviceExtensionFunction->FunctionInterfaceList[0].InterfaceInformation->InterfaceNumber;
    }

    switch (ioStack->Parameters.QueryId.IdType) {
    case BusQueryDeviceID:
        Irp->IoStatus.Information =
          (ULONG_PTR)
          USBH_BuildDeviceID(idVendor,
                             idProduct,
                             miId,
                             FALSE);
        break;

    case BusQueryHardwareIDs:

        Irp->IoStatus.Information =
            (ULONG_PTR)
            USBH_BuildHardwareIDs(idVendor,
                                  idProduct,
                                  deviceExtensionParent->DeviceDescriptor.bcdDevice,
                                  miId,
                                  FALSE);

        break;

    case BusQueryCompatibleIDs:
        //
        // always use first interface
        //
        Irp->IoStatus.Information =
            (ULONG_PTR) USBH_BuildCompatibleIDs(
                "",
                "",
                DeviceExtensionFunction->FunctionInterfaceList[0].InterfaceInformation->Class,
                DeviceExtensionFunction->FunctionInterfaceList[0].InterfaceInformation->SubClass,
                DeviceExtensionFunction->FunctionInterfaceList[0].InterfaceInformation->Protocol,
                FALSE,
                FALSE);

        break;

    case BusQueryInstanceID:

        Irp->IoStatus.Information =
            (ULONG_PTR) USBH_BuildInstanceID(&DeviceExtensionFunction->UniqueIdString[0],
                                         sizeof(DeviceExtensionFunction->UniqueIdString));
        break;

    default:
        USBH_KdBreak(("PdoBusExtension Unknown BusQueryId\n"));
        // IrpAssert: Must not change Irp->IoStatus.Status for bogus IdTypes,
        // so return original status here.
        return Irp->IoStatus.Status;
    }

    if (Irp->IoStatus.Information == 0) {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }

    return ntStatus;
}


NTSTATUS
USBH_FunctionPdoQueryDeviceText(
    IN PDEVICE_EXTENSION_FUNCTION DeviceExtensionFunction,
    IN PIRP Irp
    )
 /* ++
  *
  * Description:
  *
  * This routine is called by PnP via (IRP_MJ_PNP, IRP_MN_QUERY_CAPABILITIES).
  * Supposedly, this is a message forwarded by port device Fdo.
  *
  * Argument:
  *
  * DeviceExtensionPort - This is a a Pdo extension we created for the port
  * device. Irp - the request
  *
  * Return:
  *
  * STATUS_SUCCESS
  *
  *
  * -- */
{
    PDEVICE_OBJECT deviceObject;
    PIO_STACK_LOCATION ioStack;
    PDEVICE_EXTENSION_PARENT deviceExtensionParent;
    PDEVICE_EXTENSION_PORT deviceExtensionPort;
    PDEVICE_EXTENSION_HUB deviceExtensionHub;
    DEVICE_TEXT_TYPE deviceTextType;
    LANGID languageId;
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PUSB_STRING_DESCRIPTOR usbString;
    PWCHAR deviceText;

    PAGED_CODE();
    deviceExtensionParent = DeviceExtensionFunction->DeviceExtensionParent;
    deviceExtensionPort = (PDEVICE_EXTENSION_PORT)deviceExtensionParent->PhysicalDeviceObject->DeviceExtension;
    deviceObject = deviceExtensionPort->PortPhysicalDeviceObject;
    ioStack = IoGetCurrentIrpStackLocation(Irp);

    deviceExtensionHub = deviceExtensionPort->DeviceExtensionHub;

    deviceTextType = ioStack->
            Parameters.QueryDeviceText.DeviceTextType;

    // Validate DeviceTextType for IrpAssert

    if (deviceTextType != DeviceTextDescription &&
        deviceTextType != DeviceTextLocationInformation) {

        USBH_KdPrint((2, "'PdoQueryDeviceText called with bogus DeviceTextType\n"));
        //
        // return the original status passed to us
        //
        ntStatus = Irp->IoStatus.Status;
        goto USBH_FunctionPdoQueryDeviceTextDone;
    }

    // we don't care about the hiword
    //languageId = (USHORT) (ioStack->Parameters.QueryDeviceText.LocaleId >>16);
    // always specify english for now.
    languageId = 0x0409;
    USBH_KdPrint((2,"'PdoQueryDeviceText Pdo %x type = %x, lang = %x locale %x\n",
            deviceObject, deviceTextType, languageId, ioStack->Parameters.QueryDeviceText.LocaleId));

    //
    // see if the device supports strings, for non complient device mode
    // we won't even try
    //

    if (deviceExtensionPort->DeviceData == NULL ||
        deviceExtensionPort->DeviceDescriptor.iProduct == 0 ||
        (deviceExtensionPort->DeviceHackFlags & USBD_DEVHACK_DISABLE_SN) ||
        (deviceExtensionPort->PortPdoFlags & PORTPDO_DEVICE_ENUM_ERROR)) {
        // string descriptor
        USBH_KdBreak(("no product string\n", deviceObject));
        ntStatus = STATUS_NOT_SUPPORTED;
    }

    if (NT_SUCCESS(ntStatus)) {

        usbString = UsbhExAllocatePool(NonPagedPool, MAXIMUM_USB_STRING_LENGTH);

        if (usbString) {

            ntStatus = USBH_CheckDeviceLanguage(deviceObject,
                                                languageId);

            if (NT_SUCCESS(ntStatus)) {
                //
                // device supports are language, get the string
                //

                ntStatus = USBH_SyncGetStringDescriptor(deviceObject,
                                                        deviceExtensionPort->DeviceDescriptor.iProduct, //index
                                                        languageId, //langid
                                                        usbString,
                                                        MAXIMUM_USB_STRING_LENGTH,
                                                        NULL,
                                                        TRUE);

                if (NT_SUCCESS(ntStatus) &&
                    usbString->bLength <= sizeof(UNICODE_NULL)) {

                    ntStatus = STATUS_UNSUCCESSFUL;
                }

                if (NT_SUCCESS(ntStatus)) {
                    //
                    // return the string
                    //

                    //
                    // must use stock alloc function because the caller frees the
                    // buffer
                    //
                    // note: the descriptor header is the same size as
                    // a unicode NULL so we don't have to adjust the size
                    //

                    deviceText = ExAllocatePoolWithTag(PagedPool, usbString->bLength, USBHUB_HEAP_TAG);
                    if (deviceText) {
                        RtlZeroMemory(deviceText, usbString->bLength);
                        RtlCopyMemory(deviceText, &usbString->bString[0],
                            usbString->bLength - sizeof(UNICODE_NULL));

                        Irp->IoStatus.Information = (ULONG_PTR) deviceText;

                        USBH_KdBreak(("Returning Device Text %x\n", deviceText));
                    } else {
                        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
                    }
                }
            }

            UsbhExFreePool(usbString);
        } else {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        }
    }

USBH_FunctionPdoQueryDeviceTextDone:

    return ntStatus;
}


NTSTATUS
USBH_FunctionPdoPnP(
    IN PDEVICE_EXTENSION_FUNCTION DeviceExtensionFunction,
    IN PIRP Irp,
    IN UCHAR MinorFunction,
    IN OUT PBOOLEAN IrpNeedsCompletion
    )
 /* ++
  *
  * Description:
  *
  * This function responds to IoControl PnPPower for the PDO. This function is
  * synchronous.
  *
  * Arguments:
  *
  * DeviceExtensionPort - the PDO extension Irp - the request packet
  * uchMinorFunction - the minor function of the PnP Power request.
  *
  * Return:
  *
  * NTSTATUS
  *
  * -- */
{
    NTSTATUS ntStatus;
#if DBG
    PDEVICE_OBJECT deviceObject = DeviceExtensionFunction->FunctionPhysicalDeviceObject;
#endif
    PDEVICE_EXTENSION_PARENT deviceExtensionParent;
    PIO_STACK_LOCATION irpStack;

    PAGED_CODE();

    *IrpNeedsCompletion = TRUE;

    deviceExtensionParent = DeviceExtensionFunction->DeviceExtensionParent;
    irpStack = IoGetCurrentIrpStackLocation(Irp);
    USBH_KdPrint((2,"'PnP Power Pdo %x minor %x\n", deviceObject, MinorFunction));

    switch (MinorFunction) {
    case IRP_MN_QUERY_STOP_DEVICE:
    case IRP_MN_CANCEL_STOP_DEVICE:
    case IRP_MN_QUERY_REMOVE_DEVICE:
    case IRP_MN_CANCEL_REMOVE_DEVICE:
    case IRP_MN_QUERY_RESOURCE_REQUIREMENTS:
// Ken says take this out
//    case IRP_MN_SURPRISE_REMOVAL:
        ntStatus = STATUS_SUCCESS;
        break;

    case IRP_MN_START_DEVICE:
        USBH_KdPrint((1,
            "'Starting composite PDO %x\n",
                DeviceExtensionFunction->FunctionPhysicalDeviceObject));

        ntStatus = STATUS_SUCCESS;
        break;

    case IRP_MN_STOP_DEVICE:
        USBH_KdPrint((1,
            "'Stopping composite PDO %x\n",
                DeviceExtensionFunction->FunctionPhysicalDeviceObject));

        ntStatus = STATUS_SUCCESS;
        break;

    case IRP_MN_REMOVE_DEVICE:
        USBH_KdPrint((1,
            "'Removing composite PDO %x\n",
                DeviceExtensionFunction->FunctionPhysicalDeviceObject));

        ntStatus = STATUS_SUCCESS;
        break;

    case IRP_MN_QUERY_CAPABILITIES:
        {
        PDEVICE_CAPABILITIES deviceCapabilities;
        PIO_STACK_LOCATION ioStack;

        USBH_KdPrint((2,"'IRP_MN_QUERY_CAPABILITIES Function Pdo %x\n", deviceObject));
        ntStatus = STATUS_SUCCESS;

        ioStack = IoGetCurrentIrpStackLocation(Irp);

        deviceCapabilities = ioStack->
            Parameters.DeviceCapabilities.Capabilities;
        //
        // clone the capabilities for the parent
        //
        //

        // fill in the the device state capabilities from the
        // table we saved from the pdo.
        //

        RtlCopyMemory(&deviceCapabilities->DeviceState[0],
                      &deviceExtensionParent->DeviceState[0],
                      sizeof(deviceExtensionParent->DeviceState));

        //
        // clone the device wake capabilities for children
        // from the parent.
        //
        deviceCapabilities->DeviceWake =
            deviceExtensionParent->DeviceWake;
        deviceCapabilities->SystemWake =
            deviceExtensionParent->SystemWake;

        //
        // we will need to modify these based on information
        // returned in the power descriptor
        //

        deviceCapabilities->Removable = FALSE;
        deviceCapabilities->UniqueID = FALSE;
//      SurpriseRemovalOK is FALSE by default, and some clients (NDIS)
//      set it to true on the way down, in accordance with the DDK.
//        deviceCapabilities->SurpriseRemovalOK = FALSE;
        deviceCapabilities->RawDeviceOK = FALSE;

        }
        break;

    case IRP_MN_QUERY_ID:
        USBH_KdPrint((2,"'IRP_MN_QUERY_ID Pdo %x\n", deviceObject));
        ntStatus = USBH_FunctionPdoQueryId(DeviceExtensionFunction, Irp);
        break;

    case IRP_MN_QUERY_DEVICE_TEXT:
        USBH_KdPrint((2,"'IRP_MN_QUERY_DEVICE_TEXT Pdo %x\n", deviceObject));
        ntStatus = USBH_FunctionPdoQueryDeviceText(DeviceExtensionFunction, Irp);
        break;

    case IRP_MN_QUERY_DEVICE_RELATIONS:
        // this is a leaf node, we return the status passed
        // to us unless it is a call to TargetRelations
        if (irpStack->Parameters.QueryDeviceRelations.Type ==
            TargetDeviceRelation) {

            PDEVICE_RELATIONS deviceRelations = NULL;


            deviceRelations = ExAllocatePoolWithTag(PagedPool,
                sizeof(*deviceRelations), USBHUB_HEAP_TAG);

            if (deviceRelations == NULL) {
                ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            } else {
                ObReferenceObject(
                    DeviceExtensionFunction->FunctionPhysicalDeviceObject);
                deviceRelations->Count = 1;
                deviceRelations->Objects[0] =
                    DeviceExtensionFunction->FunctionPhysicalDeviceObject;
                ntStatus = STATUS_SUCCESS;
            }

            USBH_KdPrint((1, "'Query Target Relations (FUN) PDO %x complt\n",
                DeviceExtensionFunction->FunctionPhysicalDeviceObject));


            Irp->IoStatus.Information=(ULONG_PTR) deviceRelations;

        } else {
            ntStatus = Irp->IoStatus.Status;
        }
        break;

    case IRP_MN_QUERY_INTERFACE:

        USBH_KdPrint((1,"'IRP_MN_QUERY_INTERFACE, xface type: %x\n",
            irpStack->Parameters.QueryInterface.InterfaceType));

        // Pass this on to the parent.
        ntStatus = USBH_PassIrp(Irp, deviceExtensionParent->FunctionalDeviceObject);
        *IrpNeedsCompletion = FALSE;
        break;

    default:
        USBH_KdBreak(("PdoPnP unknown (%d) PnP message Pdo %x\n",
                      MinorFunction, deviceObject));
        ntStatus = Irp->IoStatus.Status;
    }

    USBH_KdPrint((2,"'FunctionPdoPnP exit %x\n", ntStatus));

    return ntStatus;
}


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

Routine Description:

Arguments:

Return Value:

    NT status code.

--*/
{
    PDEVICE_EXTENSION_HEADER devExtHeader;
    PDEVICE_EXTENSION_FUNCTION function;
    PDEVICE_EXTENSION_PARENT parent;
    NTSTATUS ntStatus = STATUS_CANCELLED;
    LONG pendingChildWWs;
    PIRP parentWaitWake = NULL;

    USBH_KdPrint((1,"'Function WaitWake Irp %x cancelled\n", Irp));

    USBH_ASSERT(DeviceObject);

    devExtHeader = (PDEVICE_EXTENSION_HEADER) DeviceObject->DeviceExtension;
    USBH_ASSERT(devExtHeader->ExtensionType == EXTENSION_TYPE_FUNCTION);

    function = (PDEVICE_EXTENSION_FUNCTION) devExtHeader;
    parent = function->DeviceExtensionParent;

    if (Irp != function->WaitWakeIrp) {
        //
        // Nothing to do
        // This Irp has already been taken care of.
        // We are in the process of completing this IRP in
        // USBH_ParentCompleteFunctionWakeIrps.
        //
        IoReleaseCancelSpinLock(Irp->CancelIrql);

    } else {
        function->WaitWakeIrp = NULL;
        IoSetCancelRoutine(Irp, NULL);

        pendingChildWWs = InterlockedDecrement (&parent->NumberFunctionWakeIrps);
        parentWaitWake = parent->PendingWakeIrp;
        if (0 == pendingChildWWs) {
            // Set PendingWakeIrp to NULL since we cancel it below.
            parent->PendingWakeIrp = NULL;
            parent->ParentFlags &= ~HUBFLAG_PENDING_WAKE_IRP;
        }
        IoReleaseCancelSpinLock(Irp->CancelIrql);

        PoStartNextPowerIrp(Irp);
        Irp->IoStatus.Status = STATUS_CANCELLED;
        IoCompleteRequest(Irp, IO_NO_INCREMENT);

        //
        // If there are no more outstanding WW irps, we need to cancel the WW
        // to our parent.
        //
        if (0 == pendingChildWWs) {
            IoCancelIrp (parentWaitWake);
        } else {
            ASSERT (0 < pendingChildWWs);
        }
    }
}


NTSTATUS
USBH_FunctionPdoPower(
    IN PDEVICE_EXTENSION_FUNCTION DeviceExtensionFunction,
    IN PIRP Irp,
    IN UCHAR MinorFunction
    )
 /* ++
  *
  * Description:
  *
  * This function responds to IoControl Power for the PDO. This function is
  * synchronous.
  *
  * Arguments:
  *
  * DeviceExtensionPort - the PDO extension Irp - the request packet
  * uchMinorFunction - the minor function of the PnP Power request.
  *
  * Return:
  *
  * NTSTATUS
  *
  * -- */
{
    NTSTATUS ntStatus;
#if DBG
    PDEVICE_OBJECT deviceObject = DeviceExtensionFunction->FunctionPhysicalDeviceObject;
#endif
    PIO_STACK_LOCATION irpStack;
    USHORT feature;
    KIRQL irql;
    PIRP wWIrp;
    PIRP parentWaitWake;
    LONG pendingFunctionWWs;
    PDEVICE_EXTENSION_PARENT deviceExtensionParent;
    PDRIVER_CANCEL oldCancel;

    irpStack = IoGetCurrentIrpStackLocation(Irp);
    USBH_KdPrint((2,"'Power Pdo %x minor %x\n", deviceObject, MinorFunction));
    deviceExtensionParent = DeviceExtensionFunction->DeviceExtensionParent;

    switch (MinorFunction) {

    case IRP_MN_SET_POWER:

        USBH_KdPrint((2,"'IRP_MN_SET_POWER\n"));

        //
        // we just return success here, pnp will make sure
        // all children have entred the low power state
        // before putting the parent in a low power state
        //

        //
        // send the setpower feature request here if the device
        // wants it
        //

        ntStatus = STATUS_SUCCESS;

        switch (irpStack->Parameters.Power.Type) {
        case SystemPowerState:
            USBH_KdPrint(
                (1, "'IRP_MJ_POWER PA pdo(%x) MN_SET_POWER(SystemPowerState) complt\n",
                    DeviceExtensionFunction->FunctionPhysicalDeviceObject));

            ntStatus = STATUS_SUCCESS;
            break;

        case DevicePowerState:
            ntStatus = STATUS_SUCCESS;
            USBH_KdPrint(
                (1, "'IRP_MJ_POWER PA pdo(%x) MN_SET_POWER(DevicePowerState) complt\n",
                    DeviceExtensionFunction->FunctionPhysicalDeviceObject));

            break;
        } // switch irpStack->Parameters.Power.Type
        break; //IRP_MN_SET_POWER

    case IRP_MN_QUERY_POWER:

        ntStatus = STATUS_SUCCESS;
        USBH_KdPrint(
            (1, "'IRP_MJ_POWER PA pdo(%x) MN_QUERY_POWER, status = %x complt\n",
            DeviceExtensionFunction->FunctionPhysicalDeviceObject, ntStatus));

        break;

    case IRP_MN_WAIT_WAKE:

        USBH_KdPrint(
            (1, "'enabling remote wakeup for USB child PDO (%x)\n",
                DeviceExtensionFunction->FunctionPhysicalDeviceObject));

        if (deviceExtensionParent->CurrentPowerState != PowerDeviceD0 ||
            deviceExtensionParent->ParentFlags & HUBFLAG_DEVICE_STOPPING) {

            LOGENTRY(LOG_PNP, "!WWp", deviceExtensionParent, 0, 0);

            UsbhWarning(NULL,
                        "Client driver should not be submitting WW IRPs at this time.\n",
                        TRUE);

            ntStatus = STATUS_INVALID_DEVICE_STATE;
            break;
        }

        IoAcquireCancelSpinLock(&irql);
        if (DeviceExtensionFunction->WaitWakeIrp != NULL) {
            ntStatus = STATUS_DEVICE_BUSY;
            IoReleaseCancelSpinLock(irql);

        } else {

            // set a cancel routine
            oldCancel = IoSetCancelRoutine(Irp, USBH_ParentWaitWakeCancel);
            USBH_ASSERT (NULL == oldCancel);

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

                IoSetCancelRoutine (Irp, NULL);
                IoReleaseCancelSpinLock(irql);
                ntStatus = STATUS_CANCELLED;

            } else {

                // flag this device as "enabled for wakeup"
                DeviceExtensionFunction->WaitWakeIrp = Irp;
                pendingFunctionWWs =
                    InterlockedIncrement (&deviceExtensionParent->NumberFunctionWakeIrps);
                IoMarkIrpPending(Irp);
                IoReleaseCancelSpinLock(irql);

                //
                // now we must enable the parent PDO for wakeup
                //
                if (1 == pendingFunctionWWs) {
                    // What if this fails?
                    ntStatus = USBH_ParentSubmitWaitWakeIrp(deviceExtensionParent);
                } else {
                    ntStatus = STATUS_PENDING;
                }

                ntStatus = STATUS_PENDING;
                goto USBH_FunctionPdoPower_Done;
            }
        }

        break;

    default:
        USBH_KdBreak(("PdoPnP unknown (%d) PnP message Pdo %x\n",
                      MinorFunction, deviceObject));
        //
        // return the original status passed to us
        //
        ntStatus = Irp->IoStatus.Status;
    }

    USBH_KdPrint((2,"'FunctionPdoPower exit %x\n", ntStatus));

    PoStartNextPowerIrp(Irp);
    Irp->IoStatus.Status = ntStatus;
    IoCompleteRequest(Irp, IO_NO_INCREMENT);

USBH_FunctionPdoPower_Done:

    return ntStatus;
}

NTSTATUS
USBH_ParentQCapsComplete(
    IN PDEVICE_OBJECT DeviceObject,
    IN PIRP Irp,
    IN PVOID Context
    )

/*++

Routine Description:

    Called when lower device completes Q_CAPS.
    This gives us a chance to mark the device as SurpriseRemovalOK.

Arguments:
    DeviceObject - a pointer to the device object

    Irp - a pointer to the irp

    Context - NULL ptr

Return Value:

    STATUS_SUCCESS

--*/

{
    PIO_STACK_LOCATION irpStack = IoGetCurrentIrpStackLocation(Irp);
    PDEVICE_CAPABILITIES pDevCaps = irpStack->Parameters.DeviceCapabilities.Capabilities;
    NTSTATUS ntStatus;

    USBH_KdPrint((1, "'USBH_ParentQCapsComplete\n"));

    ntStatus = Irp->IoStatus.Status;

    if (Irp->PendingReturned) {
        IoMarkIrpPending(Irp);
    }

    //
    // Set SurpriseRemoval flag to TRUE
    //
    pDevCaps->SurpriseRemovalOK = TRUE;

    return ntStatus;
}



NTSTATUS
USBH_ParentPnP(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent,
    IN PIRP Irp,
    IN UCHAR MinorFunction
    )
 /* ++
  *
  * Description:
  *
  * This function responds to IoControl PnP for the FDO. This function is
  * synchronous.
  *
  * Arguments:
  *
  * DeviceExtensionParent - the FDO extension pIrp - the request packet
  * MinorFunction - the minor function of the PnP Power request.
  *
  * Return:
  *
  * NTSTATUS
  *
  * -- */
{
    NTSTATUS ntStatus;
    PDEVICE_OBJECT deviceObject;
    PIO_STACK_LOCATION irpStack;

    irpStack = IoGetCurrentIrpStackLocation(Irp);

    deviceObject = DeviceExtensionParent->FunctionalDeviceObject;
    USBH_KdPrint((2,"'PnP Fdo %x minor %x\n", deviceObject, MinorFunction));

    switch (MinorFunction) {
    case IRP_MN_START_DEVICE:
        USBH_KdBreak(("'IRP_MN_START_DEVICE Parent Fdo %x\n", deviceObject));
        // we get here as a result of re-start.
        // note: our parent hub already checked to see if the device is the same.
        Irp->IoStatus.Status = STATUS_SUCCESS;
        ntStatus = USBH_ParentFdoStartDevice(DeviceExtensionParent, Irp, FALSE);
        break;

    case IRP_MN_STOP_DEVICE:
        USBH_KdPrint((2,"'IRP_MN_STOP_DEVICE Fdo %x\n", deviceObject));
        Irp->IoStatus.Status = STATUS_SUCCESS;
        ntStatus = USBH_ParentFdoStopDevice(DeviceExtensionParent, Irp);
        break;

    case IRP_MN_REMOVE_DEVICE:
        USBH_KdPrint((2,"'IRP_MN_REMOVE_DEVICE Fdo %x\n", deviceObject));
        Irp->IoStatus.Status = STATUS_SUCCESS;
        ntStatus = USBH_ParentFdoRemoveDevice(DeviceExtensionParent, Irp);
        break;

//
// This one should be passed down.  Let the default case handle it.
//
//    case IRP_MN_QUERY_PNP_DEVICE_STATE:
//        USBH_KdPrint((2,"IRP_MN_QUERY_PNP_DEVICE_STATE Pdo %x\n", deviceObject));
//        ntStatus = STATUS_SUCCESS;
//        break;

    case IRP_MN_QUERY_DEVICE_RELATIONS:
        switch (irpStack->Parameters.QueryDeviceRelations.Type) {
        case BusRelations:

            ntStatus = USBH_ParentQueryBusRelations(DeviceExtensionParent, Irp);
            break;

        case TargetDeviceRelation:
            //
            // this one gets passed on
            //

            USBH_KdPrint((1, "'Query Relations, TargetRelations (PAR) %x\n",
                DeviceExtensionParent->PhysicalDeviceObject));

            ntStatus = USBH_PassIrp(Irp,
                                    DeviceExtensionParent->TopOfStackDeviceObject);
            break;

        default:

            USBH_KdPrint((1, "'Query Relations (?) (PAR) %x pass on\n",
                DeviceExtensionParent->PhysicalDeviceObject));

            ntStatus = USBH_PassIrp(Irp,
                                    DeviceExtensionParent->TopOfStackDeviceObject);

        }
        break;

    case IRP_MN_QUERY_CAPABILITIES:
        USBH_KdPrint((1, "'Query Capabilities (PAR) %x\n",
            DeviceExtensionParent->PhysicalDeviceObject));

        IoCopyCurrentIrpStackLocationToNext(Irp);

        // Set up a completion routine to handle marking the IRP.
        IoSetCompletionRoutine(Irp,
                               USBH_ParentQCapsComplete,
                               DeviceExtensionParent,
                               TRUE,
                               TRUE,
                               TRUE);

        // Now pass down the IRP.

        ntStatus = IoCallDriver(DeviceExtensionParent->TopOfStackDeviceObject, Irp);

        break;

    case IRP_MN_QUERY_STOP_DEVICE:
    case IRP_MN_CANCEL_STOP_DEVICE:
    case IRP_MN_QUERY_REMOVE_DEVICE:
    case IRP_MN_CANCEL_REMOVE_DEVICE:
// Ken says take this out
//    case IRP_MN_SURPRISE_REMOVAL:
    case IRP_MN_DEVICE_USAGE_NOTIFICATION:
        // IrpAssert: Must set IRP status before passing IRP down.
        Irp->IoStatus.Status = STATUS_SUCCESS;
        // fall through

        //
        // Pass it down to Pdo to handle all other MN functions
        //
    default:
        USBH_KdPrint((2,"'Query/Cancel/Power request on parent fdo %x  %x\n",
                      deviceObject, MinorFunction));
        ntStatus = USBH_PassIrp(Irp,
                                DeviceExtensionParent->TopOfStackDeviceObject);
        break;
    }

    USBH_KdPrint((2,"'ParentPnP exit %x\n", ntStatus));
    return ntStatus;
}


NTSTATUS
USBH_ParentPower(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent,
    IN PIRP Irp,
    IN UCHAR MinorFunction
    )
 /* ++
  *
  * Description:
  *
  * This function responds to IoControl Power for the FDO. This function is
  * synchronous.
  *
  * Arguments:
  *
  * DeviceExtensionParent - the FDO extension pIrp - the request packet
  * MinorFunction - the minor function of the PnP Power request.
  *
  * Return:
  *
  * NTSTATUS
  *
  * -- */
{
    NTSTATUS ntStatus;
    PDEVICE_OBJECT deviceObject;
    PIO_STACK_LOCATION irpStack;

    irpStack = IoGetCurrentIrpStackLocation(Irp);

    deviceObject = DeviceExtensionParent->FunctionalDeviceObject;
    USBH_KdPrint((2,"'Power Fdo %x minor %x\n", deviceObject, MinorFunction));

    switch (MinorFunction) {

    case IRP_MN_QUERY_POWER:

        USBH_KdPrint(
            (1, "'IRP_MJ_POWER PA fdo(%x) MN_QUERY_POWER\n",
            DeviceExtensionParent->FunctionalDeviceObject));

        IoCopyCurrentIrpStackLocationToNext(Irp);
        PoStartNextPowerIrp(Irp);
        //
        // must pass this on to our PDO
        //
        ntStatus = PoCallDriver(DeviceExtensionParent->TopOfStackDeviceObject,
                                Irp);

        break;

    case IRP_MN_SET_POWER:

        USBH_KdPrint(
            (1, "'IRP_MJ_POWER PA fdo(%x) MN_QUERY_POWER\n",
            DeviceExtensionParent->FunctionalDeviceObject));

        switch (irpStack->Parameters.Power.Type) {
        case SystemPowerState:
            {
            POWER_STATE powerState;

            USBH_KdPrint(
                (1, "IRP_MJ_POWER PA fdo(%x) MN_SET_POWER(SystemPowerState)\n",
                    DeviceExtensionParent->FunctionalDeviceObject));

            if (irpStack->Parameters.Power.State.SystemState ==
                PowerSystemWorking) {
                powerState.DeviceState = PowerDeviceD0;
            } else if (DeviceExtensionParent->ParentFlags &
                       HUBFLAG_ENABLED_FOR_WAKEUP) {
                //
                // based on the system power state
                // request a setting to the appropriate
                // Dx state.
                //
                powerState.DeviceState =
                    DeviceExtensionParent->DeviceState[irpStack->Parameters.Power.State.SystemState];

                //
                // These tables should have already been fixed up by the root hub
                // (usbd.sys) to not contain an entry of unspecified.
                //
                ASSERT (PowerDeviceUnspecified != powerState.DeviceState);

                USBH_KdPrint((2,"'Parent System state maps to device state 0x%x\n",
                              powerState.DeviceState));

            } else {
                TEST_TRAP();
                powerState.DeviceState = PowerDeviceD3;
            } // irpStack->Parameters.Power.State.SystemState

            //
            // only make the request if it is for a differnt power
            // state then the one we are in.
            //

            if (powerState.DeviceState !=
                DeviceExtensionParent->CurrentPowerState) {

                DeviceExtensionParent->PowerIrp = Irp;
                ntStatus = PoRequestPowerIrp(DeviceExtensionParent->PhysicalDeviceObject,
                                          IRP_MN_SET_POWER,
                                          powerState,
                                          USBH_FdoDeferPoRequestCompletion,
                                          DeviceExtensionParent,
                                          NULL);

            } else {
                IoCopyCurrentIrpStackLocationToNext(Irp);
                PoStartNextPowerIrp(Irp);
                ntStatus = PoCallDriver(DeviceExtensionParent->TopOfStackDeviceObject,
                                        Irp);
            }
            }
            break;

        case DevicePowerState:

            USBH_KdPrint(
                (1, "IRP_MJ_POWER PA fdo(%x) MN_SET_POWER(DevicePowerState)\n",
                    DeviceExtensionParent->FunctionalDeviceObject));

            DeviceExtensionParent->CurrentPowerState =
                    irpStack->Parameters.Power.State.DeviceState;


            LOGENTRY(LOG_PNP, "prD>", DeviceExtensionParent, DeviceExtensionParent->CurrentPowerState , 0);
            //
            // all of our pdos need to be at or below the
            // expected D-state
            //

            IoCopyCurrentIrpStackLocationToNext(Irp);
            PoStartNextPowerIrp(Irp);
            ntStatus = PoCallDriver(DeviceExtensionParent->TopOfStackDeviceObject,
                                    Irp);

            USBH_KdPrint((2,"'Parent Device Power State PoCallDriver() = %x\n",
                               ntStatus));

            break;
        }

        break; // MN_SET_POWER

    default:
        USBH_KdPrint((2,"'Power request on parent not handled, fdo %x  %x\n",
                      deviceObject, MinorFunction));

        IoCopyCurrentIrpStackLocationToNext(Irp);
        PoStartNextPowerIrp(Irp);
        ntStatus = PoCallDriver(DeviceExtensionParent->TopOfStackDeviceObject,
                                Irp);
        break;
    }

    USBH_KdPrint((2,"'ParentPnP exit %x\n", ntStatus));
    return ntStatus;
}

NTSTATUS
USBH_ParentDispatch(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent,
    IN PIRP Irp
    )
 /* ++
  *
  * Description:
  *
  * Handles calls to a FDO associated with a composite device
  *
  * Arguments:
  *
  * Return:
  *
  * NTSTATUS
  *
  * -- */
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PIO_STACK_LOCATION ioStackLocation;    // our stack location
    PDEVICE_OBJECT deviceObject;

    USBH_KdPrint((2,"'FdoDispatch DeviceExtension %x Irp %x\n",
        DeviceExtensionParent, Irp));
    deviceObject = DeviceExtensionParent->FunctionalDeviceObject;

    //
    // Get a pointer to IoStackLocation so we can retrieve parameters.
    //
    ioStackLocation = IoGetCurrentIrpStackLocation(Irp);

    //
    // the called functions will complete the irp if necessary
    //

    switch (ioStackLocation->MajorFunction) {
    case IRP_MJ_CREATE:

        USBH_KdPrint((2,"'IRP_MJ_CREATE\n"));
        USBH_CompleteIrp(Irp, STATUS_SUCCESS);
        break;

    case IRP_MJ_CLOSE:

        USBH_KdPrint((2,"'IRP_MJ_CLOSE\n"));
        USBH_CompleteIrp(Irp, STATUS_SUCCESS);
        break;

    case IRP_MJ_DEVICE_CONTROL:
        //
        // Note: if we ever do find a reason to handle this, be sure to
        // not forward IOCTL_KS_PROPERTY / KSPROPSETID_DrmAudioStream /
        // KSPROPERTY_DRMAUDIOSTREAM_SETCONTENTID to next driver!  Otherwise
        // this might not be DRM compliant.
        //
        USBH_KdPrint((2,"'IRP_MJ_DEVICE_CONTROL\n"));
        UsbhWarning(NULL,"Should not be hitting this code\n", FALSE);
        ntStatus = STATUS_UNSUCCESSFUL;
        USBH_CompleteIrp(Irp, ntStatus);
        break;

    case IRP_MJ_INTERNAL_DEVICE_CONTROL:

        USBH_KdPrint((2,"'InternlDeviceControl IOCTL unknown pass on\n"));
        UsbhWarning(NULL,"Should not be hitting this code\n", FALSE);
        ntStatus = STATUS_UNSUCCESSFUL;
        USBH_CompleteIrp(Irp, ntStatus);
        break;

    case IRP_MJ_PNP:

        USBH_KdPrint((2,"'IRP_MJ_PNP\n"));

        ntStatus = USBH_ParentPnP(DeviceExtensionParent, Irp, ioStackLocation->MinorFunction);
        break;

    case IRP_MJ_POWER:

        USBH_KdPrint((2,"'IRP_MJ_POWER\n"));

        ntStatus = USBH_ParentPower(DeviceExtensionParent, Irp, ioStackLocation->MinorFunction);
        break;

    case IRP_MJ_SYSTEM_CONTROL:

        USBH_KdPrint((2,"'IRP_MJ_SYSTEM_CONTROL\n"));
#ifdef WMI_SUPPORT
        ntStatus =
            USBH_SystemControl ((PDEVICE_EXTENSION_FDO) DeviceExtensionParent, Irp);
#else
        ntStatus = USBH_PassIrp(Irp,
                                DeviceExtensionParent->TopOfStackDeviceObject);
#endif
        break;

    default:
        //
        // Unknown Irp -- complete with error
        //
        USBH_KdBreak(("Unknown Irp for Fdo %x Irp_Mj %x\n",
                  deviceObject, ioStackLocation->MajorFunction));
        ntStatus = STATUS_NOT_IMPLEMENTED;
        USBH_CompleteIrp(Irp, ntStatus);
        break;
    }

    USBH_KdPrint((2,"' exit USBH_ParentDispatch Object %x Status %x\n",
                  deviceObject, ntStatus));

    //
    // always return a status code
    //

    return ntStatus;
}


NTSTATUS
USBH_FunctionUrbFilter(
    IN PDEVICE_EXTENSION_FUNCTION DeviceExtensionFunction,
    IN PIRP Irp
    )
 /*
  * Description:
  *
  * Arguments:
  *
  * Return:
  *
  * NTSTATUS
  *
  * -- */
{
    NTSTATUS ntStatus;
    PIO_STACK_LOCATION ioStackLocation;    // our stack location
    PDEVICE_EXTENSION_PARENT deviceExtensionParent;
    PURB urb;
    USHORT function;

    USBH_KdPrint((2,"'USBH_FunctionUrbFilter DeviceExtension %x Irp %x\n",
        DeviceExtensionFunction, Irp));

    deviceExtensionParent = DeviceExtensionFunction->DeviceExtensionParent;

    LOGENTRY(LOG_PNP, "fURB", DeviceExtensionFunction, deviceExtensionParent,
        deviceExtensionParent->ParentFlags);

    ioStackLocation = IoGetCurrentIrpStackLocation(Irp);
    urb = ioStackLocation->Parameters.Others.Argument1;

    // check the command code code the URB

    function = urb->UrbHeader.Function;

    if (deviceExtensionParent->CurrentPowerState !=
        PowerDeviceD0) {

        // the child devices should not be passing in urbs
        // unless th eparent is in D0

        UsbhWarning(NULL,
           "Parent Not in D0.\n",
           TRUE);

    }

    switch(function) {
    case URB_FUNCTION_SELECT_CONFIGURATION:
        {
        //
        // if the requested config matches the current config
        // then go ahead and return the current interface
        // information for all interfaces requested.
        //
        PUSBD_INTERFACE_INFORMATION interface;

        if (urb->UrbSelectConfiguration.ConfigurationDescriptor == NULL) {
            USBH_KdBreak(("closing config on a composite device\n"));

            //
            // closing the configuration,
            // just return success.
            //

            urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
            ntStatus = STATUS_SUCCESS;
        } else {
            ULONG i;

            //
            // Normally the URB will contain only one interface.
            // the special case is audio which may contain two
            // so we have to have a check to handle this.
            //

            interface = &urb->UrbSelectConfiguration.Interface;

USBH_FunctionUrbFilter_Next:

            USBH_KdPrint((2,"'interface = %x\n",
                    interface));

            //
            // should validate the requested interface against the
            // current config.
            //
            USBH_KdBreak(("need some validation here!\n"));

            USBH_ASSERT(urb->UrbSelectConfiguration.ConfigurationDescriptor->bConfigurationValue
                    == deviceExtensionParent->CurrentConfig);

            // find the interface we are interested in
            for (i=0; i< DeviceExtensionFunction->InterfaceCount; i++) {
                PFUNCTION_INTERFACE functionInterface;

                functionInterface =
                    &DeviceExtensionFunction->FunctionInterfaceList[i];

                USBH_KdPrint((2,"'functionInterface  = %x, %x\n",
                   functionInterface, functionInterface->InterfaceInformation));

                if (functionInterface->InterfaceInformation->InterfaceNumber ==
                    interface->InterfaceNumber) {
                    break;
                }
            }

            if (i < DeviceExtensionFunction->InterfaceCount) {
                PFUNCTION_INTERFACE functionInterface;

                functionInterface =
                    &DeviceExtensionFunction->FunctionInterfaceList[i];

                if (functionInterface->InterfaceInformation->AlternateSetting !=
                    interface->AlternateSetting) {

                    PURB iUrb;
                    NTSTATUS localStatus;
                    PUSBD_INTERFACE_INFORMATION localInterface;
                    USHORT siz;

                    // client is requesting a different alternate setting
                    // we need to do a select_interface

                    siz =
                        (USHORT)(GET_SELECT_INTERFACE_REQUEST_SIZE(interface->NumberOfPipes));

                    iUrb = UsbhExAllocatePool(NonPagedPool, siz);
                    if (iUrb) {
                        localInterface = &iUrb->UrbSelectInterface.Interface;

                        iUrb->UrbSelectInterface.Hdr.Function =
                            URB_FUNCTION_SELECT_INTERFACE;
                        iUrb->UrbSelectInterface.Hdr.Length = siz;
                        iUrb->UrbSelectInterface.ConfigurationHandle =
                            DeviceExtensionFunction->ConfigurationHandle;

                        USBH_KdPrint((2,"'localInterface = %x\n",
                            localInterface));

                        RtlCopyMemory(localInterface,
                                      interface,
                                      interface->Length);

                        localStatus = USBH_SyncSubmitUrb(
                            deviceExtensionParent->TopOfStackDeviceObject,
                            iUrb);


                        UsbhExFreePool(functionInterface->InterfaceInformation);

                        functionInterface->InterfaceInformation =
                            UsbhExAllocatePool(NonPagedPool,
                                               interface->Length);

                        RtlCopyMemory(functionInterface->InterfaceInformation,
                                      localInterface,
                                      localInterface->Length);

                        UsbhExFreePool(iUrb);
                        iUrb = NULL;
                    }

                }

                USBH_ASSERT(interface->Length ==
                      functionInterface->InterfaceInformation->Length);

                RtlCopyMemory(interface,
                              functionInterface->InterfaceInformation,
                              functionInterface->InterfaceInformation->Length);

                urb->UrbSelectConfiguration.ConfigurationHandle =
                    DeviceExtensionFunction->ConfigurationHandle;

                urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
                ntStatus = STATUS_SUCCESS;
            } else {
                ntStatus = STATUS_INVALID_PARAMETER;
            }

            //
            // check for multiple interfaces e.g. audio
            //

            if (DeviceExtensionFunction->InterfaceCount > 1) {

                interface = (PUSBD_INTERFACE_INFORMATION)
                    (((PUCHAR) interface) + interface->Length);

                if ((PUCHAR)interface < (((PUCHAR) urb) +
                    urb->UrbSelectConfiguration.Hdr.Length)) {
                    goto USBH_FunctionUrbFilter_Next;
                }
            }
        }

        USBH_CompleteIrp(Irp, ntStatus);
        }

        break;

    case URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE:
        {
        PUCHAR userBuffer = NULL;
        ULONG bytesReturned;

        //
        // if we requesting the configuration descriptor then we
        // will return it based on the information in our extension.
        //
        if (urb->UrbControlDescriptorRequest.DescriptorType ==
            USB_CONFIGURATION_DESCRIPTOR_TYPE) {

            if (urb->UrbControlDescriptorRequest.TransferBufferMDL) {
                ntStatus = STATUS_INVALID_PARAMETER;
            } else {
                userBuffer =
                    urb->UrbControlDescriptorRequest.TransferBuffer;

                ntStatus = USBH_BuildFunctionConfigurationDescriptor(
                                DeviceExtensionFunction,
                                userBuffer,
                                urb->UrbControlDescriptorRequest.TransferBufferLength,
                                &bytesReturned);

                urb->UrbControlDescriptorRequest.TransferBufferLength =
                    bytesReturned;

                urb->UrbHeader.Status = USBD_STATUS_SUCCESS;
            }

            USBH_CompleteIrp(Irp, ntStatus);

        } else {
            ntStatus = USBH_PassIrp(Irp,
                                    deviceExtensionParent->TopOfStackDeviceObject);
        }
        }
        break;

    default:
        //
        // forward the request to the parents PDO
        //
        ntStatus = USBH_PassIrp(Irp,
                                deviceExtensionParent->TopOfStackDeviceObject);
        break;
    }

    return ntStatus;
}


VOID
USBH_CancelAllIrpsInList(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent
    )
 /*
  * Description:
  *
  *     This function walks the list of devices and cancels all the queued
  *     ResetIrps in the list.
  *
  * Arguments:
  *
  * Return:
  *
  *
  * -- */
{
    PSINGLE_LIST_ENTRY          listEntry;
    PDEVICE_EXTENSION_FUNCTION  deviceExtensionFunction;

    listEntry = DeviceExtensionParent->FunctionList.Next;

    while (listEntry) {
        deviceExtensionFunction =
            CONTAINING_RECORD(listEntry,
                              DEVICE_EXTENSION_FUNCTION,
                              ListEntry);
        ASSERT_FUNCTION(deviceExtensionFunction);

        if (deviceExtensionFunction->ResetIrp) {
            USBH_CompleteIrp(deviceExtensionFunction->ResetIrp, STATUS_UNSUCCESSFUL);
            deviceExtensionFunction->ResetIrp = NULL;
        }

        listEntry = listEntry->Next;
    }
}


VOID
USBH_CompResetTimeoutWorker(
    IN PVOID Context)
 /* ++
  *
  * Description:
  *
  * Work item scheduled to handle a composite reset timeout.
  *
  *
  * Arguments:
  *
  * Return:
  *
  * -- */
{
    PUSBH_COMP_RESET_TIMEOUT_WORK_ITEM  workItemCompResetTimeout;
    PDEVICE_EXTENSION_PARENT            deviceExtensionParent;

    workItemCompResetTimeout = Context;
    deviceExtensionParent = workItemCompResetTimeout->DeviceExtensionParent;

    USBH_KdPrint((2,"'CompReset timeout\n"));
    LOGENTRY(LOG_PNP, "CRTO", deviceExtensionParent, 0, 0);

    USBH_KdPrint((2,"'*** (CRTW) WAIT parent mutex %x\n", deviceExtensionParent));
    KeWaitForSingleObject(&deviceExtensionParent->ParentMutex,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);
    USBH_KdPrint((2,"'*** (CRTW) WAIT parent mutex done %x\n", deviceExtensionParent));

    USBH_CancelAllIrpsInList(deviceExtensionParent);

    USBH_KdPrint((2,"'*** (CRTW) RELEASE parent mutex %x\n", deviceExtensionParent));
    KeReleaseSemaphore(&deviceExtensionParent->ParentMutex,
                       LOW_REALTIME_PRIORITY,
                       1,
                       FALSE);

    UsbhExFreePool(workItemCompResetTimeout);
}


VOID
USBH_CompResetTimeoutDPC(
    IN PKDPC Dpc,
    IN PVOID DeferredContext,
    IN PVOID SystemArgument1,
    IN PVOID SystemArgument2
    )
/*++

Routine Description:

    This routine runs at DISPATCH_LEVEL IRQL.



Arguments:

    Dpc - Pointer to the DPC object.

    DeferredContext -

    SystemArgument1 - not used.

    SystemArgument2 - not used.

Return Value:

    None.

--*/
{
    PCOMP_RESET_TIMEOUT_CONTEXT compResetTimeoutContext = DeferredContext;
    PDEVICE_EXTENSION_PARENT deviceExtensionParent =
                                compResetTimeoutContext->DeviceExtensionParent;
    BOOLEAN cancelFlag;
    PUSBH_COMP_RESET_TIMEOUT_WORK_ITEM workItemCompResetTimeout;

    USBH_KdPrint((2,"'COMP_RESET_TIMEOUT\n"));

    // Take SpinLock here so that main routine won't write CancelFlag
    // in the timeout context while we free the timeout context.

    KeAcquireSpinLockAtDpcLevel(&deviceExtensionParent->ParentSpinLock);

    cancelFlag = compResetTimeoutContext->CancelFlag;
    deviceExtensionParent->CompResetTimeoutContext = NULL;

    KeReleaseSpinLockFromDpcLevel(&deviceExtensionParent->ParentSpinLock);

    UsbhExFreePool(compResetTimeoutContext);

    if (!cancelFlag) {
        //
        // Schedule a work item to process this.
        //
        workItemCompResetTimeout = UsbhExAllocatePool(NonPagedPool,
                                    sizeof(USBH_COMP_RESET_TIMEOUT_WORK_ITEM));

        if (workItemCompResetTimeout) {

            workItemCompResetTimeout->DeviceExtensionParent = deviceExtensionParent;

            ExInitializeWorkItem(&workItemCompResetTimeout->WorkQueueItem,
                                 USBH_CompResetTimeoutWorker,
                                 workItemCompResetTimeout);

            LOGENTRY(LOG_PNP, "crER", deviceExtensionParent,
                &workItemCompResetTimeout->WorkQueueItem, 0);

            ExQueueWorkItem(&workItemCompResetTimeout->WorkQueueItem,
                            DelayedWorkQueue);

            // The WorkItem is freed by USBH_CompResetTimeoutWorker()
            // Don't try to access the WorkItem after it is queued.
        }
    }
}


BOOLEAN
USBH_ListReadyForReset(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent
    )
 /*
  * Description:
  *
  *     This function walks the list of devices to see if we are ready
  *     to do the actual reset.
  *
  * Arguments:
  *
  * Return:
  *
  * TRUE if we're ready, FALSE if we're not.
  *
  * -- */
{
    PSINGLE_LIST_ENTRY          listEntry;
    PDEVICE_EXTENSION_FUNCTION  deviceExtensionFunction;

    listEntry = DeviceExtensionParent->FunctionList.Next;

    while (listEntry) {
        deviceExtensionFunction =
            CONTAINING_RECORD(listEntry,
                              DEVICE_EXTENSION_FUNCTION,
                              ListEntry);
        ASSERT_FUNCTION(deviceExtensionFunction);

        if (!deviceExtensionFunction->ResetIrp)
            return FALSE;

        listEntry = listEntry->Next;
    }

    return TRUE;
}


NTSTATUS
USBH_ResetParentPort(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent
    )
/*++

Routine Description:

    Calls the parent device to reset its port.

Arguments:

Return Value:

    NTSTATUS

--*/
{
    NTSTATUS ntStatus, status = STATUS_SUCCESS;
    PIRP irp;
    KEVENT event;
    IO_STATUS_BLOCK ioStatus;

    USBH_KdPrint((2,"'CompReset parent port\n"));
    LOGENTRY(LOG_PNP, "CRPP", DeviceExtensionParent, 0, 0);

    //
    // issue a synchronous request
    //

    KeInitializeEvent(&event, NotificationEvent, FALSE);

    irp = IoBuildDeviceIoControlRequest(
                IOCTL_INTERNAL_USB_RESET_PORT,
                DeviceExtensionParent->TopOfStackDeviceObject,
                NULL,
                0,
                NULL,
                0,
                TRUE, /* INTERNAL */
                &event,
                &ioStatus);

    if (irp == NULL) {
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    //
    // Call the class driver to perform the operation.  If the returned status
    // is PENDING, wait for the request to complete.
    //

    ntStatus = IoCallDriver(DeviceExtensionParent->TopOfStackDeviceObject,
                            irp);

    if (ntStatus == STATUS_PENDING) {

        status = KeWaitForSingleObject(
                       &event,
                       Suspended,
                       KernelMode,
                       FALSE,
                       NULL);
    } else {
        ioStatus.Status = ntStatus;
    }

    ntStatus = ioStatus.Status;

    return ntStatus;
}


VOID
USBH_CompositeResetPortWorker(
    IN PVOID Context)
 /* ++
  *
  * Description:
  *
  * Work item scheduled to process a composite port reset.
  *
  *
  * Arguments:
  *
  * Return:
  *
  * -- */
{
    PUSBH_COMP_RESET_WORK_ITEM  workItemCompReset;
    PSINGLE_LIST_ENTRY          listEntry;
    PDEVICE_EXTENSION_PARENT    deviceExtensionParent;
    PDEVICE_EXTENSION_FUNCTION  deviceExtensionFunction;

    USBH_KdPrint((2,"'Composite Reset Executing!\n"));

    workItemCompReset = Context;
    deviceExtensionParent = workItemCompReset->DeviceExtensionParent;

    LOGENTRY(LOG_PNP, "CRW_", deviceExtensionParent, 0, 0);

    // Send reset to parent (IoCallDriver)

    USBH_ResetParentPort(deviceExtensionParent);

    // Now, complete all Irps in list and set the Irps in the list to NULL.

    USBH_KdPrint((2,"'*** (CRW) WAIT parent mutex %x\n", deviceExtensionParent));
    KeWaitForSingleObject(&deviceExtensionParent->ParentMutex,
                          Executive,
                          KernelMode,
                          FALSE,
                          NULL);
    USBH_KdPrint((2,"'*** (CRW) WAIT parent mutex done %x\n", deviceExtensionParent));

    listEntry = deviceExtensionParent->FunctionList.Next;

    while (listEntry) {
        deviceExtensionFunction =
            CONTAINING_RECORD(listEntry,
                              DEVICE_EXTENSION_FUNCTION,
                              ListEntry);
        ASSERT_FUNCTION(deviceExtensionFunction);

        // Although ResetIrp should usually be set here, we check anyway in
        // case it had already been completed in USBH_CompleteAllIrpsInList.
        //
        if (deviceExtensionFunction->ResetIrp) {
            USBH_CompleteIrp(deviceExtensionFunction->ResetIrp, STATUS_SUCCESS);
            deviceExtensionFunction->ResetIrp = NULL;
        }

        listEntry = listEntry->Next;
    }

    USBH_KdPrint((2,"'*** (CRW) RELEASE parent mutex %x\n", deviceExtensionParent));
    KeReleaseSemaphore(&deviceExtensionParent->ParentMutex,
                       LOW_REALTIME_PRIORITY,
                       1,
                       FALSE);

    UsbhExFreePool(workItemCompReset);
}


NTSTATUS
USBH_FunctionPdoDispatch(
    IN PDEVICE_EXTENSION_FUNCTION DeviceExtensionFunction,
    IN PIRP Irp
    )
 /*
  * Description:
  *
  *     This function handles calls to PDOs we have created
  *     since we are the bottom driver for the PDO it is up
  *     to us to complete the irp -- with one exception.
  *
  *     api calls to the USB stack are forwarded directly
  *     to the PDO for the root hub which is owned by the USB
  *     HC.
  *
  * Arguments:
  *
  * Return:
  *
  * NTSTATUS
  *
  * -- */
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PIO_STACK_LOCATION ioStackLocation;    // our stack location
    PDEVICE_OBJECT deviceObject;
    PDEVICE_EXTENSION_PARENT deviceExtensionParent;
    PCOMP_RESET_TIMEOUT_CONTEXT compResetTimeoutContext = NULL;
    LARGE_INTEGER dueTime;
    KIRQL irql;
    BOOLEAN bCompleteIrp;

    USBH_KdPrint((2,"'FunctionPdoDispatch DeviceExtension %x Irp %x\n",
        DeviceExtensionFunction, Irp));
    deviceObject = DeviceExtensionFunction->FunctionPhysicalDeviceObject;
    deviceExtensionParent = DeviceExtensionFunction->DeviceExtensionParent;

    //
    // Get a pointer to IoStackLocation so we can retrieve parameters.
    //
    ioStackLocation = IoGetCurrentIrpStackLocation(Irp);

    switch (ioStackLocation->MajorFunction) {
    case IRP_MJ_CREATE:
        USBH_KdPrint((2,"'PARENT PDO IRP_MJ_CREATE\n"));
        ntStatus = STATUS_SUCCESS;
        USBH_CompleteIrp(Irp, ntStatus);
        break;

    case IRP_MJ_CLOSE:
        USBH_KdPrint((2,"'PARENT PDO IRP_MJ_CLOSE\n"));
        ntStatus = STATUS_SUCCESS;
        USBH_CompleteIrp(Irp, ntStatus);
        break;

    case IRP_MJ_INTERNAL_DEVICE_CONTROL:
        {
        ULONG ioControlCode;

        USBH_KdPrint((2,"'Internal Device Control\n"));

        if (deviceExtensionParent->ParentFlags & HUBFLAG_DEVICE_STOPPING) {
            UsbhWarning(NULL,
                "Client Device Driver is sending requests to a device that has been removed.\n",
                FALSE);

            ntStatus = STATUS_DEVICE_REMOVED;
            USBH_CompleteIrp(Irp, ntStatus);
            break;
        }

        ioControlCode = ioStackLocation->Parameters.DeviceIoControl.IoControlCode;

        switch (ioControlCode) {
        case IOCTL_INTERNAL_USB_GET_PORT_STATUS:
            USBH_KdPrint((2,"'Composite GetPortStatus, pass on\n"));
            ntStatus = USBH_PassIrp(Irp, deviceExtensionParent->TopOfStackDeviceObject);
            break;

        case IOCTL_INTERNAL_USB_RESET_PORT:

            LOGENTRY(LOG_PNP, "fRES", deviceExtensionParent, 0, 0);

            USBH_KdPrint((2,"'Composite Reset Requested\n"));
            if (deviceExtensionParent->CurrentPowerState !=
                 PowerDeviceD0) {

                // the child devices should not be resetting
                // unless the parent is in D0

                UsbhWarning(NULL,
                   "Parent Not in D0.\n",
                   TRUE);

            }

            if (DeviceExtensionFunction->ResetIrp) {
                ntStatus = STATUS_UNSUCCESSFUL;
                USBH_CompleteIrp(Irp, ntStatus);
            } else {
                ntStatus = STATUS_PENDING;

                USBH_KdPrint((2,"'***WAIT parent mutex %x\n", deviceExtensionParent));
                KeWaitForSingleObject(&deviceExtensionParent->ParentMutex,
                                      Executive,
                                      KernelMode,
                                      FALSE,
                                      NULL);
                USBH_KdPrint((2,"'***WAIT parent mutex done %x\n", deviceExtensionParent));

                DeviceExtensionFunction->ResetIrp = Irp;
                if (USBH_ListReadyForReset(deviceExtensionParent)) {

                    PUSBH_COMP_RESET_WORK_ITEM workItemCompReset;

                    //
                    // "Cancel" watchdog timer.
                    //
                    // Take SpinLock here so that DPC routine won't free
                    // the timeout context while we write the CancelFlag
                    // in the timeout context.
                    //
                    KeAcquireSpinLock(&deviceExtensionParent->ParentSpinLock,
                                        &irql);

                    if (deviceExtensionParent->CompResetTimeoutContext) {

                        compResetTimeoutContext = deviceExtensionParent->CompResetTimeoutContext;
                        compResetTimeoutContext->CancelFlag = TRUE;

                        if (KeCancelTimer(&compResetTimeoutContext->TimeoutTimer)) {
                            //
                            // We cancelled the timer before it could run.  Free the context.
                            //
                            deviceExtensionParent->CompResetTimeoutContext = NULL;
                            UsbhExFreePool(compResetTimeoutContext);
                        }
                    }

                    KeReleaseSpinLock(&deviceExtensionParent->ParentSpinLock,
                                        irql);

                    //
                    // Schedule a work item to process this reset.
                    //
                    workItemCompReset = UsbhExAllocatePool(NonPagedPool,
                                            sizeof(USBH_COMP_RESET_WORK_ITEM));

                    USBH_ASSERT(workItemCompReset);

                    if (workItemCompReset) {

                        workItemCompReset->DeviceExtensionParent = deviceExtensionParent;

                        ExInitializeWorkItem(&workItemCompReset->WorkQueueItem,
                                             USBH_CompositeResetPortWorker,
                                             workItemCompReset);

                        LOGENTRY(LOG_PNP, "rCMP", deviceExtensionParent,
                            &workItemCompReset->WorkQueueItem, 0);

                        ExQueueWorkItem(&workItemCompReset->WorkQueueItem,
                                        DelayedWorkQueue);

                        // The WorkItem is freed by USBH_CompositeResetPortWorker()
                        // Don't try to access the WorkItem after it is queued.

                    } else {
                        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
                    }

                } else if (!deviceExtensionParent->CompResetTimeoutContext) {
                    // Start watchdog timer if not already started.
                    //
                    // When timer expires, timer routine should
                    // complete all Irps in the list with an error
                    // and clear the Irps in the list.

                    USBH_KdPrint((2,"'Start composite port reset timeout\n"));
                    compResetTimeoutContext = UsbhExAllocatePool(NonPagedPool,
                                            sizeof(*compResetTimeoutContext));

                    USBH_ASSERT(compResetTimeoutContext);

                    if (compResetTimeoutContext) {

                        compResetTimeoutContext->CancelFlag = FALSE;

                        // Maintain links between the device extension and the
                        // timeout context.
                        deviceExtensionParent->CompResetTimeoutContext = compResetTimeoutContext;
                        compResetTimeoutContext->DeviceExtensionParent = deviceExtensionParent;

                        KeInitializeTimer(&compResetTimeoutContext->TimeoutTimer);
                        KeInitializeDpc(&compResetTimeoutContext->TimeoutDpc,
                                        USBH_CompResetTimeoutDPC,
                                        compResetTimeoutContext);

                        dueTime.QuadPart = -10000 * COMP_RESET_TIMEOUT;

                        KeSetTimer(&compResetTimeoutContext->TimeoutTimer,
                                   dueTime,
                                   &compResetTimeoutContext->TimeoutDpc);

                    } else {
                        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
                    }
                }

                if (ntStatus == STATUS_PENDING) {
                    IoMarkIrpPending(Irp);
                } else {
                    USBH_CompleteIrp(Irp, ntStatus);
                }

                USBH_KdPrint((2,"'***RELEASE parent mutex %x\n", deviceExtensionParent));
                KeReleaseSemaphore(&deviceExtensionParent->ParentMutex,
                                   LOW_REALTIME_PRIORITY,
                                   1,
                                   FALSE);
            }
            break;

        case IOCTL_INTERNAL_USB_GET_ROOTHUB_PDO:
            TEST_TRAP(); //shouldn't see this
            break;

        case IOCTL_INTERNAL_USB_SUBMIT_URB:
            ntStatus = USBH_FunctionUrbFilter(DeviceExtensionFunction, Irp);
            break;

        case IOCTL_INTERNAL_USB_GET_BUS_INFO:
            // this api returns some BW info that drivers
            // may need -- pass it on
            ntStatus = USBH_PassIrp(Irp, deviceExtensionParent->TopOfStackDeviceObject);
            break;

        default:
            USBH_KdPrint((2,"'InternalDeviceControl IOCTL unknown pass on\n"));
            ntStatus = STATUS_INVALID_PARAMETER;
            USBH_CompleteIrp(Irp, ntStatus);
        }
        break;

        }

    case IRP_MJ_PNP:

        USBH_KdPrint((2,"'IRP_MJ_PNP\n"));
        ntStatus = USBH_FunctionPdoPnP(DeviceExtensionFunction, Irp,
                        ioStackLocation->MinorFunction, &bCompleteIrp);

        if (bCompleteIrp) {
            USBH_CompleteIrp(Irp, ntStatus);
        }
        break;

    case IRP_MJ_POWER:

        USBH_KdPrint((2,"'IRP_MJ_POWER\n"));
        ntStatus = USBH_FunctionPdoPower(DeviceExtensionFunction, Irp, ioStackLocation->MinorFunction);
        break;

    case IRP_MJ_SYSTEM_CONTROL:

        USBH_KdPrint((2,"'IRP_MJ_SYSTEM_CONTROL\n"));
        ntStatus = STATUS_NOT_SUPPORTED;
        USBH_CompleteIrp(Irp, ntStatus);
        break;

    case IRP_MJ_DEVICE_CONTROL:
        //
        // Note: if we ever do find a reason to handle this, be sure to
        // not forward IOCTL_KS_PROPERTY / KSPROPSETID_DrmAudioStream /
        // KSPROPERTY_DRMAUDIOSTREAM_SETCONTENTID to next driver!  Otherwise
        // this might not be DRM compliant.
        //
        USBH_KdBreak(("Unhandled IRP_MJ_DEVICE_CONTROL for Pdo %x Irp_Mj %x\n",
                       deviceObject, ioStackLocation->MajorFunction));
        ntStatus = STATUS_INVALID_PARAMETER;
        USBH_CompleteIrp(Irp, ntStatus);
        break;

    default:

        // Unknown Irp, shouldn't be here.
        USBH_KdBreak(("Unhandled Irp for Pdo %x Irp_Mj %x\n",
                       deviceObject, ioStackLocation->MajorFunction));
        ntStatus = STATUS_INVALID_PARAMETER;
        USBH_CompleteIrp(Irp, ntStatus);
        break;

    }

    USBH_KdPrint((2,"' exit USBH_FunctionPdoDispatch Object %x -- Status %x\n",
                  deviceObject, ntStatus));

    return ntStatus;
}


NTSTATUS
USBH_BuildFunctionConfigurationDescriptor(
    IN PDEVICE_EXTENSION_FUNCTION DeviceExtensionFunction,
    IN OUT PUCHAR Buffer,
    IN ULONG BufferLength,
    OUT PULONG BytesReturned
    )
 /*
  * Description:
  *
  *  This function creates a configuration descriptor (with all interface &
  *  endpoints) for a give function.
  *
  * Arguments:
  *
  *     Buffer - buffer to put descriptor in
  *
  *     BufferLength - max size of this buffer.
  *
  * Return:
  *
  * NTSTATUS
  *
  * -- */
{
    NTSTATUS ntStatus = STATUS_SUCCESS;
    PDEVICE_EXTENSION_PARENT deviceExtensionParent;
    PUSB_CONFIGURATION_DESCRIPTOR configurationDescriptor;
    PVOID scratch;
    ULONG length, i;
    PUCHAR pch;

    USBH_KdPrint((2,"'USBH_BuildFunctionConfigurationDescriptor\n"));

    deviceExtensionParent = DeviceExtensionFunction->DeviceExtensionParent;

    //
    // scratch area to build descriptor in
    //

    *BytesReturned = 0;

    configurationDescriptor = deviceExtensionParent->ConfigurationDescriptor;
    if (!configurationDescriptor || !configurationDescriptor->wTotalLength) {
        return STATUS_INVALID_PARAMETER;
    }

    scratch = UsbhExAllocatePool(PagedPool, configurationDescriptor->
                                 wTotalLength);

    if (scratch) {

        configurationDescriptor = scratch;
        pch = scratch;

        length = sizeof(USB_CONFIGURATION_DESCRIPTOR);
        RtlCopyMemory(pch,
                      deviceExtensionParent->ConfigurationDescriptor,
                      length);
        pch+=length;

        //
        // now copy the interfaces
        //

        for (i=0; i< DeviceExtensionFunction->InterfaceCount; i++) {
            PFUNCTION_INTERFACE functionInterface;

            functionInterface =
                &DeviceExtensionFunction->FunctionInterfaceList[i];

            RtlCopyMemory(pch,
                          functionInterface->InterfaceDescriptor,
                          functionInterface->InterfaceDescriptorLength);


            pch+=functionInterface->InterfaceDescriptorLength;
            length+=functionInterface->InterfaceDescriptorLength;
        }

        configurationDescriptor->bNumInterfaces = (UCHAR) DeviceExtensionFunction->InterfaceCount;
        configurationDescriptor->wTotalLength = (USHORT) length;

        //
        // now copy what we can in to the user buffer
        //
        if (BufferLength >= configurationDescriptor->wTotalLength) {
            *BytesReturned = configurationDescriptor->wTotalLength;
        } else {
            *BytesReturned = BufferLength;
        }

        RtlCopyMemory(Buffer,
                      scratch,
                      *BytesReturned);

        USBH_KdBreak(("'USBH_BuildFunctionConfigurationDescriptor, buffer = %x scratch = %x\n",
            Buffer, scratch));

        UsbhExFreePool(scratch);

    } else {
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
    }

    return ntStatus;
}


VOID
USBH_ParentCompleteFunctionWakeIrps(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent,
    IN NTSTATUS NtStatus
    )
/*++

Routine Description:

    Called when a wake irp completes for a hub
    Propagates the wake irp completion to all the function (children).

Arguments:

    DeviceObject - Pointer to the device object for the class device.

Return Value:

    The function value is the final status from the operation.

--*/
{
    PDEVICE_EXTENSION_FUNCTION deviceExtensionFunction;
    PSINGLE_LIST_ENTRY listEntry;
    PIRP irp;
    KIRQL irql;
    LONG pendingFunctionWWs;
    ULONG i;
    PIRP irpArray[128];     // Limited to 127 functions in the list.

    LOGENTRY(LOG_PNP, "fWWc", DeviceExtensionParent, NtStatus, 0);

    //
    // Here we are walking the list of child PDOs, which should never change.
    // (The number of interfaces on a USB device is fixed so long as the parent
    // is here the number of children stay constant.)
    //
    // Therefore we need no protection for parent->FunctionList.
    //
    // Wrongo!  The list may not change, but the WW IRPs attributed to the
    // list can, so we must take the spinlock here.

    IoAcquireCancelSpinLock(&irql);

    listEntry = DeviceExtensionParent->FunctionList.Next;
    i = 0;

    while (listEntry) {

        deviceExtensionFunction =
             CONTAINING_RECORD(listEntry,
                               DEVICE_EXTENSION_FUNCTION,
                               ListEntry);

        irp = deviceExtensionFunction->WaitWakeIrp;
        deviceExtensionFunction->WaitWakeIrp = NULL;
        if (irp) {

            IoSetCancelRoutine(irp, NULL);

            pendingFunctionWWs =
                InterlockedDecrement(&DeviceExtensionParent->NumberFunctionWakeIrps);

            if (0 == pendingFunctionWWs) {
                LOGENTRY(LOG_PNP, "fWWx", DeviceExtensionParent,
                    DeviceExtensionParent->PendingWakeIrp, 0);
                DeviceExtensionParent->PendingWakeIrp = NULL;
                DeviceExtensionParent->ParentFlags &= ~HUBFLAG_PENDING_WAKE_IRP;
            }

            irpArray[i++] = irp;
        }

        listEntry = listEntry->Next;
    }

    irpArray[i] = NULL;     // Terminate array

    IoReleaseCancelSpinLock(irql);

    USBH_ASSERT(DeviceExtensionParent->PendingWakeIrp == NULL);

    // Ok, we have queued all the function wake IRPs and have released the
    // cancel spinlock.  Let's complete all the IRPs.

    i = 0;

    while (irpArray[i]) {
        USBH_KdPrint((1,"'completing function WaitWake irp(%x) for PARENT VID %x, PID %x\n\n",
                        NtStatus,
                        DeviceExtensionParent->DeviceDescriptor.idVendor, \
                        DeviceExtensionParent->DeviceDescriptor.idProduct));

        irpArray[i]->IoStatus.Status = NtStatus;
        PoStartNextPowerIrp(irpArray[i]);
        IoCompleteRequest(irpArray[i], IO_NO_INCREMENT);

        i++;
    }
}


NTSTATUS
USBH_ParentPoRequestD0Completion(
    IN PDEVICE_OBJECT       DeviceObject,
    IN UCHAR                MinorFunction,
    IN POWER_STATE          PowerState,
    IN PVOID                Context,
    IN PIO_STATUS_BLOCK     IoStatus
    )
/*++

Routine Description:

    Called when a wake irp completes for a hub

Arguments:

    DeviceObject - Pointer to the device object for the class device.

    Irp - Irp completed.

    Context - Driver defined context.

Return Value:

    The function value is the final status from the operation.

--*/
{
    NTSTATUS ntStatus;
    PDEVICE_EXTENSION_PARENT deviceExtensionParent = Context;

    ntStatus = IoStatus->Status;

    USBH_KdPrint((1,"'WaitWake D0 completion(%x) for PARENT VID %x, PID %x\n",
        ntStatus,
        deviceExtensionParent->DeviceDescriptor.idVendor, \
        deviceExtensionParent->DeviceDescriptor.idProduct));

    LOGENTRY(LOG_PNP, "pWD0", deviceExtensionParent,
                              deviceExtensionParent->PendingWakeIrp,
                              0);
    //
    // Device has been powered on.  Now we must complete the function that
    // caused the parent to awake.
    //
    // Since of course we cannot tell them apart we must complete all function
    // WW Irps.
    //
    USBH_ParentCompleteFunctionWakeIrps(deviceExtensionParent, STATUS_SUCCESS);

    return ntStatus;
}


NTSTATUS
USBH_ParentWaitWakeIrpCompletion(
    IN PDEVICE_OBJECT       DeviceObject,
    IN UCHAR                MinorFunction,
    IN POWER_STATE          PowerState,
    IN PVOID                Context,
    IN PIO_STATUS_BLOCK     IoStatus
    )
/*++

Routine Description:

    Called when a wake irp completes for a composite device

Arguments:

    DeviceObject - Pointer to the device object for the class device.

    Irp - Irp completed.

    Context - Driver defined context.

Return Value:

    The function value is the final status from the operation.

--*/
{
    NTSTATUS ntStatus;
    PDEVICE_EXTENSION_PARENT deviceExtensionParent = Context;
    POWER_STATE powerState;

    ntStatus = IoStatus->Status;

    USBH_KdPrint((1,"'WaitWake completion(%x) for PARENT VID %x, PID %x\n",
        ntStatus,
        deviceExtensionParent->DeviceDescriptor.idVendor, \
        deviceExtensionParent->DeviceDescriptor.idProduct));

    LOGENTRY(LOG_PNP, "pWWc", deviceExtensionParent,
                              ntStatus,
                              0);

    // first we power our device back on

    if (NT_SUCCESS(ntStatus)) {

        powerState.DeviceState = PowerDeviceD0;

        PoRequestPowerIrp(deviceExtensionParent->PhysicalDeviceObject,
                              IRP_MN_SET_POWER,
                              powerState,
                              USBH_ParentPoRequestD0Completion,
                              deviceExtensionParent,
                              NULL);

        // USBH_ParentPoRequestD0Completion must complete the
        // wake irp
        ntStatus = STATUS_SUCCESS;
    } else {
        // complete the child wake requests with an error
        USBH_ParentCompleteFunctionWakeIrps(deviceExtensionParent,
                                            ntStatus);
    }

    return ntStatus;
}


NTSTATUS
USBH_ParentSubmitWaitWakeIrp(
    IN PDEVICE_EXTENSION_PARENT DeviceExtensionParent
    )
/*++

Routine Description:

    called when a child Pdo is enabled for wakeup, this
    function allocates a wait wake irp and passes it to
    the parents PDO.


Arguments:

Return Value:

--*/
{
    PIRP irp;
    NTSTATUS ntStatus;
    POWER_STATE powerState;

    USBH_ASSERT (NULL == DeviceExtensionParent->PendingWakeIrp);

    LOGENTRY(LOG_PNP, "prWI", DeviceExtensionParent,
             0,
             0);

    USBH_ASSERT(DeviceExtensionParent->PendingWakeIrp == NULL);

    DeviceExtensionParent->ParentFlags |= HUBFLAG_PENDING_WAKE_IRP;
    powerState.DeviceState = DeviceExtensionParent->SystemWake;

    ntStatus = PoRequestPowerIrp(DeviceExtensionParent->PhysicalDeviceObject,
                                 IRP_MN_WAIT_WAKE,
                                 powerState,
                                 USBH_ParentWaitWakeIrpCompletion,
                                 DeviceExtensionParent,
                                 &irp);

    if (ntStatus == STATUS_PENDING) {
        if (DeviceExtensionParent->ParentFlags & HUBFLAG_PENDING_WAKE_IRP) {
            DeviceExtensionParent->PendingWakeIrp = irp;
        }
    }
    USBH_KdPrint((2,
                  "'ntStatus from PoRequestPowerIrp for wait_wake to parent PDO = 0x%x\n", ntStatus));

    return ntStatus;
}